CSS
HTML5
Chrome

DevToolsのTimelineパネルを見ながら、レンダリングの仕組みを理解する

More than 1 year has passed since last update.

CYBIRDエンジニア Advent Calendar 2015 23日目を担当する@cy-mitsukiです。

昨日は@cy-yuto-kurosawaさんのサーベルが振りたかったでした。
私も先週から寝込んでいたのでLightsaber Escapeを知らなかったんですけど、なにこれすごい!
Webの技術はどんどん進化していってますねー。@cy-yuto-kurosawaさんの続報に期待します!

はじめに

ブラウザのレンダリングの仕組みについてはHTML5 Rocksで詳細な解説がされています。
とても丁寧に書かれているのですが、ちゃんと理解できている自信がないです。。
そこで、DevToolsのTimelineパネルを見ながら、実際にどのような処理が行われているのか確認していきます。

この確認手法を知っておくと、Webパフォーマンスの改善にも役立てることができますよ!

なお本記事では、レンダリングエンジンのメインフローのみを扱います。
クリティカルレンダリングパスとも呼ばれている下記の領域です。

image4.jpg
画像引用元:Optimizing the Critical Rendering Path

入門的な内容にしたいので、HTMLとCSSの話が中心です。
JavaScriptに関しては取り扱いません。

あと、Chrome Canary (version 49)を使用していきますよ。

やや推測も入っているので、間違いがありましたらご指摘お願いします。m(_ _)m

Timelineパネルの使い方

初期状態では何も表示されていないので、ページをリロードしましょう。
すると、ページの表示で発生した処理(イベント)レコードを見ることができます。
ページ表示後の処理を確認したい場合は、Recordボタンを押すと記録が始まります。
本記事ではEvent Logタブを開いて、発生時間でソートします。

image-1.jpg

レコードの種類

レコードは4種類にカテゴライズされています。
内容は下記の通りです。

image2.jpg

レコードの種類 内容
Loading (青) ネットワークの送受信。HTMLのパース
Scripting (橙) JavaScriptの実行。イベント処理や関数呼び出し
Rendering (紫) DOMのスタイルやレイアウトに関する処理
Painting (緑) 画面への描画に関する処理

HTMLを読み込んで、仕組みを理解する

今回読み込むHTMLは下記になります。非常にシンプルです。

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Sample</title>
  </head>
  <body>
    <p>Text</p>
  </body>
</html>

結果

image3.jpg

解説

上から順に要所を絞って見ていきます。
「Finish Loading」レコードまでの説明は省略します。

Parse HTML
取得したHTMLファイルを解析しています。
レコードを展開すると、5つのScriptingと2つのRenderingが発生しています。
上から2つ目のEventはDOMContentLoadedだったので、Parse HTMLのレコード(Self Time 0.3ms)でDOMツリーの構築は完了しているようです。

Recalculate Style
要素(DOM)とスタイル(CSSOM)を結びつけています。
ここでレンダーツリーの構築が行われています。
※ 今回のHTMLにCSSは書いていませんが、ブラウザにはデフォルトスタイルがあります。

Layout
レンダーツリーの各DOMのスタイル情報をもとに、レイアウト(位置)を決定しています。

Update Layer Tree
GPUが処理を行うレイヤを更新しています。
レイヤという概念が元記事に書かれていなかった気がするので後ほど説明します。

Paint
DOMに対してピクセルを埋め込んで画面に描画します。

Composite Layers
GPUが処理するレイヤーを描画しています。

以上が、レンダリングの基本的な流れになります。

レイヤとは何か

レイヤとは、GPUが処理する領域を意味します。
レイヤは下記の手順で可視化することができます。

  1. chrome://flags/ を開く
  2. 合成された表示レイヤの境界線 (#composited-layer-borders) を有効にする

これを実施すると、ドキュメント上にタイルが表示されるようになります。
(このタイルは直接関係ありません)

オレンジ色の境界線で囲われた部分がレイヤになります。
ドキュメント全体もレイヤになっていることが分かります。
(ページスクロールもGPUの処理です)

image12.jpg

右の画面は、p要素にtransformプロパティをあてています。
tranformプロパティはGPUで処理されていることが分かります。

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Sample</title>
    <style>
    .text {
      width: 100px;
      transform: rotateY(30deg) rotateX(-30deg);
    }
    </style>
  </head>
  <body>
    <p class="text">Text</p>
  </body>
</html>

GPUって何?という方は、こちらの記事がとても分かりやすかったので紹介します。
スムーズなアニメーションを実装するコツと仕組みを説明するよ。CPUとGPUを理解しハードウェアアクセラレーションを駆使するのだ!(Frontrend Advent Calendar 2013 – 06日目) | Ginpen.com

Layout、Paint、Compositeに影響するCSSプロパティ

Layoutは位置や大きさに関するプロパティが影響します。
width, height, margin, padding, top/left...などです。

Paintは色や見た目に関するプロパティが影響します。
color, background, border-radius, box-shadow...などです。

Composite (Layer)transform, opacityなどのプロパティが影響します。

影響するCSSプロパティを調査をしたいときはCSS Triggers...が便利です。

バッドプラクティスの検証

なんとなくわかってきたところで、よく耳にする簡単なバッドプラクティスを検証してみます。
今回は2つの例を取り上げます。

img要素にサイズを明示しない

img要素のサイズを明示しないとパフォーマンスが落ちるという話

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Sample</title>
  </head>
  <body>
    <img src="image.jpg">
  </body>
</html>

結果

img1.jpg
※ Scriptingの表示をOFFにしています

Parse HTML内のSend Requestで画像をリクエストしています。
画像のレスポンスを受け取っているのは、Layoutが終わったあとなので、
受け取った画像のサイズをもとに、再度Layoutからのレンダリングをおこなっています。
これは非効率ですね。

最初から画像のサイズを指定しておけば、再度Layoutは行われません。

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Sample</title>
  </head>
  <body>
    <img src="image.jpg" width="240px" height="240px">
  </body>
</html>

最適化した結果

img2.jpg

アニメーションにGPUを使用しない

アニメーションはGPUで実装しないとパフォーマンスが落ちるという話

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Sample</title>
    <style>
    .box {
      width: 50px;
      height: 50px;
      background-color: blue;
      position: absolute;
      animation: anime 5s ease -2s infinite alternate;
    }
    @keyframes anime {
        0% {left: 0;}
      100% {left: 200px;}
    }
    </style>
  </head>
  <body>
    <div class="box"></div>
  </body>
</html>

結果

anim1.jpg

Recalculate Styleからの処理を何度も繰り返しているので、
パフォーマンスが大きく低下していることが一目同然です。
この繰り返し処理は永遠に続きます。

このような処理はレイヤを生成してくれるGPUに任せるのが定石ですね。

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Sample</title>
    <style>
    .box {
      width: 50px;
      height: 50px;
      background-color: blue;
      position: absolute;
      animation: anime 5s ease -2s infinite alternate;
    }
    @keyframes anime {
        0% {transform: translate(0px, 0px);}
      100% {transform: translate(200px, 0px);}
    }
    </style>
  </head>
  <body>
    <div class="box"></div>
  </body>
</html>

最適化した結果

anim2.jpg

最後に

いかがでしたでしょうか。
今日はTimelineパネルの見方と、「レンダリング」で行われていることについてまとめてみました。

シンプルなHTMLとCSSのみでの解説でしたが、実際にはもっと大量で複雑なレコードが生成されます。
まずは基本をしっかり理解して、パフォーマンスの改善に取り組んでいきましょう!
より深い情報を知りたい場合は、このあとに記載する参考資料を要チェックです。

明日のCYBIRDエンジニア Advent Calendar 2015は、 1日目も担当した@gotyooooさんの再登場です。
1日目では最近のCYBIRDゲームインフラ環境を晒してくれましたが、次回はどんな記事になるのでしょうか。楽しみですね。

参考資料