Poznajemy, co to znaczy render blocking, czy CSS jest render-blocking i jak sprawić, aby nie był plus przykład do czego mogłoby to się nam przydać w praktyce. Do dzieła.
Ok, co to znaczy render blocking? Przypomnijmy sobie lekcję o Vue.js, jedną z pierwszych, cykl życia aplikacji:
const app = Vue.createApp({
data() {
return {
name: "John Doe",
age: 30,
link: "https://www.google.com",
raw: "<strong>Hello World!</stron>"
};
},
beforeCreate() {
alert('I will be created in a moment');
},
created() {
console.log('I am created');
}
});
app.mount('#app');
Hook beforeCreated dostał nie console.loga, ale alert. I zanim aplikacja została utworzona wyświetlił się ten alert, zaś z tyłu kod Vue był w postaci markupu. I wisiał tak, do czasu aż klikniemy ok.
Dobra, zróbmy coś takiego:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link id="myCss" rel="stylesheet" href="./renderblocking.css">
</head>
<body>
<h1>Hello world</h1>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Omnis delectus magnam non sint, labore cupiditate molestiae
cum dicta accusamus veniam aspernatur temporibus tenetur provident praesentium blanditiis ducimus.
Cupiditate, deleniti laboriosam!</p>
<script>
alert("Hello world")
</script>
</body>
</html>
No i w tym CSS dajmy np. body background color na tomato.
Ok, alert jest zanim się pojawi cokolwiek na stronie, zdaje się. Jest mega render-blocking i odpala się natychmiast, prawdopodobnie zanim jeszcze mamy DOMContentLoaded (to zależy od prędkości naszego komputera, na 99% zanim) i zaczyna blokować inne rzeczy.
Ok, ale pal 6 ten alert. Czy CSS jest render blocking?
No, a co to jest render tree? Już wiemy, że tworzony jest DOM i CSSOM, wiemy co to jest, wiemy też (to są uproszczenia, całe lekcje o tym były) że DOM + CSSOM = Render Tree. I to jest renderowane.
Więc raczej oczywiste, że CSS jest render-blocking.
Ok, fajnie, ustawmy media na print:
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link id="myCss" rel="stylesheet" href="./renderblocking.css" media="print">
</head>
I już nie jest render-blocking. Problem polega na tym, że tego arkusza teraz nie ma w ogóle. No chyba, że będziemy chcieli wydrukować stronę.
Ale możemy go przywrócić, jak już DOM się załaduje, po to mu nadaliśmy id:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link id="myCss" rel="stylesheet" href="./renderblocking.css" media="print">
</head>
<body>
<h1>Hello world</h1>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Omnis delectus magnam non sint, labore cupiditate molestiae cum dicta accusamus veniam aspernatur temporibus tenetur provident praesentium blanditiis ducimus. Cupiditate, deleniti laboriosam!</p>
<script>
alert("Hello world")
window.addEventListener("DOMContentLoaded", function(){
let css = document.querySelector("#myCss");
css.setAttribute("media", "all");
});
</script>
</body>
</html>
I już nie blokuje nam CSS rendera. Pierwszy render będzie miał miejsce jeszcze bez tego arkusza. Elementy na stronie się pokażą, DOMContentLoaded odpalony (jak nam nie wystarcza, możemy słuchać na readystatechange), następnie zmieniamy typ media na all i dopiero wtedy leci ten arkusz.
Ok, ale do czego nam to? Cóż, zmieniamy arkusz:
h1 {
transition: color .3s;
}
h1:hover {
color: teal
}
No i? Do czego to prowadzi?
Przypomnijmy sobie lekcję o analizie malware, tam był taki fajny kod:
function touchSupport() {
var ts = "ontouchstart" in window, d = document;
if (ts) return ts;
try {
d.createEvent("TouchEvent");
ts = true;
} catch (r) {
ts = false;
}
return ts;
}
Ok, to teraz tak:
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link id="myCss" rel="stylesheet" href="./renderblocking.css" media="print">
</head>
<body>
<h1>Hello world</h1>
<p>Lorem (...)</p>
<script>
function touchSupport() {
var ts = "ontouchstart" in window, d = document;
if (ts) return ts;
try {
d.createEvent("TouchEvent");
ts = true;
} catch (r) {
ts = false;
}
return ts;
}
window.addEventListener("DOMContentLoaded", function(){
let isComputer = !(touchSupport());
console.log(isComputer)
if(isComputer){
let css = document.querySelector("#myCss");
css.setAttribute("media", "all");
}
});
</script>
</body>
</html>
Mamy takie coś, że hover styles ładują się tylko dla tych urządzeń, które nie mają touch events. Znaczy – mają pointer events. W sumie tak to się powinno tworzyć, white list nie blacklist, ale ok.
Dobra, sprawdźmy, czy tak jest, ale tę naszą funkcję będziemy musieli trochę ogłupić:
<script>
window.ontouchstart = function(){return "hello world";};
function touchSupport() {
return "ontouchstart" in window
}
window.addEventListener("DOMContentLoaded", function(){
let isComputer = !(touchSupport());
console.log(isComputer)
if(isComputer){
let css = document.querySelector("#myCss");
css.setAttribute("media", "all");
}
});
</script>
Teraz niby z mobilnego wchodzimy (tak naprawdę nadpisaliśmy coś w window i ogłupiliśmy funkcję sprawdzającą) i nie mamy hoverów, które na mobilnym są nam zupełnie niepotrzebne.
Prosty i głupi przykład (nie taki do końca głupi), ale pokazuje do czego możemy chcieć użyć nieblokujący CSS.