DevToolを使って画面が描画されるまでを追ってみた
今回は、実際に記述したコードが画面に描画されるまでの過程をDevToolを使って追ってみたいと思います。
今まで、知識としてParseやRenderingなどがあることは知っていても、書いたコードが実際にどんな過程を経て描画されているのか視覚的に見たことはありませんでした。(「やりたいなー」と思っていながらズルズル時間が流れていってしまっていた。。。)
CSS-in-JSとCSS Moduleのperformanceも比較したいなと思ったのですが、まずは一番シンプルなコードで試してみます。
では、早速やってみましょう。
この記事に書いていないこと
- Performance向上のためのtips
- DevToolの詳しい使い方
- CSS-in-JSとCSS Moduleのperformanceの比較
- ブラウザに画面が描画されるまでの説明
気になる方はこちらから(少し古いけど)
How browsers work
シンプルなhtmlを表示させてみる
cssもJavaScriptもない本当にシンプルなhtmlです
html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div>html</div>
</body>
</html>
Performanceタブから計測を行います。
結果はこちら。Parse HTML
に0.11ms
を要しています。
ちなみに、Parse HTML
の後に2つScriptがあるのですが、これは前方からreadystatechangeとDOMContentLoadedです。
続いて、Rendering
が始まります。
Process | Time |
---|---|
Recalculate Style | 0.12ms |
Event:readystatechange | 3μs |
Event:load | 5μs |
Event:pageshow | 2μs |
Layout | 0.22ms |
Pre-Paint | 47μs |
Pre-Paintが初めてみる言葉だったので調べてみました。
Pre-paint: compute property trees and invalidate any existing display lists and GPU texture tiles as appropriate.
既存のDisplay list(実際にピクセルへと描画する時に使用されるもの。Render Treeが元になっている)とGPUで持ってるtextureをいい感じにinvalidateするらしい。
詳しいことは後日調べてみます🙇♂️
DevtoolのPerformance panelがRenderingNG
のステップに追いついていないためPre-Paintを追加するためのやりとりがありました。
UpdateLayerTreeがPre-Paintになったみたいです。
Rendering
が終わるとPainting
が始まります。
Process | Time |
---|---|
Paint | 41μs |
Composite Layers | 0.18ms |
これで一連の流れが終了しました。
ProcessとTimeのまとめ
Process | Time |
---|---|
Parse HTML | 0.11ms |
Event:readystatechange | 3μs |
Event:DOMContentLoaded | 1μs |
Recalculate Style | 0.12ms |
Event:readystatechange | 3μs |
Event:load | 5μs |
Event:pageshow | 2μs |
Layout | 0.22ms |
Pre-Paint | 47μs |
Paint | 41μs |
Composite Layers | 0.18ms |
次はStyleをあててみましょう。
Styleをあてて表示させてみる
htmlはStyle sheetの読み込みとclass付与以外は変わりません。
html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="test">html</div>
</body>
</html>
css
.test {
background: red;
width: 100px;
height: 100px;
}
Performanceを測ってみるとEvent:DOMContentLoaded
の後にParse Stylesheet
が追加されていました。
Process | Time |
---|---|
Parse Stylesheet | 0.26ms |
正にこの部分ですね。
CSS parsing
Rendering
以降を見てみるとcssがないときに比べて全体的に時間がかかっていることがわかります。(Event系は省いています。)
Process | Time(css有り) | Time(css無し) |
---|---|---|
Recalculate Style | 0.30ms | 0.12ms |
Layout | 0.13ms | 0.22ms |
Pre-Paint | 50μs | 47μs |
Paint | 56μs | 41μs |
Composite Layers | 0.18ms | 0.18ms |
ちなみに、cssは例外を除き左からではなく右から解析されるためセレクタの指定方法によっては遅くなることがあります。以下のコードで試してみます。
html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="test">
html
<div class="test-2">htmlの子</div>
</div>
<div>シンプルなhtml</div> // これが99個
</body>
</html>
css
/* 速い */
.test .test-2 {
background: blue;
width: 100px;
height: 100px;
}
/* 遅い */
.test div {
background: blue;
width: 100px;
height: 100px;
}
1.5倍くらい遅くなっていることがわかります。
これは、全てのdivタグを走査し.testを親にもつDOMを探す計算をしているためです。
Process | Time(速い) | Time(遅い) |
---|---|---|
Recalculate Style | 0.26ms | 0.40ms |
Layout | 0.69ms | 1.00ms |
Pre-Paint | 0.11ms | 0.17ms |
Paint | 0.38ms | 0.65ms |
Composite Layers | 0.24ms | 0.45ms |
JavaScriptを実行してみる
最後にjavascriptを実行してみます。
html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="./index.js"></script>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div>html</div>
</body>
</html>
JavaScript
const main = async () => {
console.log("main");
};
main();
結果はこちら。Evaluate Scriptが表示されました。
赤枠の部分は、実行してるmain関数
です。
Process | Time |
---|---|
Evaluate Script | 0.26ms |
main() | 16μs |
ちなみに、Compile Script
とCompile Code
の違いはこちらです。
Compile Script
・・・ブラウザがスクリプトファイルのコードをコンパイル
Compile Code
・・・ブラウザが関数のコードをコンパイル
なので、下記のようなファイルだった場合Compile Code
は2回走ります。
function hoge() {
console.log("hoge");
}
function foo() {
console.log("foo");
}
hoge();
foo();
次は意図的にParse block
を引き起こしてみます。問題を顕著にしたいためCPUを6x slowdown
にし、著しく性能を落とした上で実行してみます。
html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="./index.js"></script>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div>html</div>
</body>
</html>
JavaScript
const main = () => {
for (const i in [...Array(1000)]) {
console.log(i);
}
};
main();
結果はこちら。
Parse HTML
の中でEvaluate Script
が実行されていることがわかります。
画像からも分かる通り、Self Time
は2.50ms
なのに対し、Total Time
では58.25ms
の時間を要しています。
このような事象を回避する方法として「JavaScriptはbodyの最後に追加する」なんてことが解決方法として挙げられます。
どのような結果になるか試してみます。
html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div>html</div>
<script src="./index.js"></script>
</body>
</html>
Parse HTML
の中でのEvaluate Script
の実行はなくなりました。
しかし、Painting
が終了した後にEvaluate Script
が実行されているため、JavaScriptのコードによってはParse HTML
-> Rendering
-> Painting
を再度引き起こすかもしれません。やってみましょう。
document.write
を使ってDOM構造を変化させてみます。
JavaScript
const main = () => {
for (const i in [...Array(1000)]) {
console.log(i);
}
document.write("<div>main</div>");
};
main();
Evaluate Script
の中でParse HTML
が実行されRendering
-> Painting
が引き起こされました。
document.write
は極端な例ですが、昨今のリッチなUIではanimationも不可欠な要素になってきており、animationの処理によっては同じようにRendering
-> Painting
が引き起こされfpsの低下につながり画面のカクつきを起こしてしまうことがあります。
まとめ
今回は、ブラウザに表示されるまでの間にどんな事が起きているのかを実際にDevToolを使って追ってみました。
今まで、テキストの知識としてしか理解できていなかったParseやRenderingなどの各種のフェーズが、実際に記述したコードによってどのように変化するのかを知る良い機会になり解像度が上がりました。
また、このような文献は圧倒的に英語の記事が多いため「英語の勉強しなきゃな・・・」と痛感させられました。。。
次は、実際にCSS-in-JSとCSS Moduleを使ってPerformanceの比較を行ってみたいと思います。
ありがとうございました。
参考文献
Discussion