ホーム自己紹介ブログ
NO.83
DATE2022. 07. 03

ブラウザのレイアウトとペイントを知る

ブラウザのレンダリングエンジンにおけるレイアウトやペイントについて気になったので、調べました。 その内容をまとめます。レンダリングエンジンは、Chrome の Blink を題材とします。

レンダリングエンジンの処理工程

レンダリングエンジンの処理工程は、次の記事が参考になります。

  • https://web.dev/rendering-performance/
  • https://blog.leap-in.com/lets-learn-how-to-browser-works/
  • https://silver-birder.github.io/blog/contents/learning_browser_engine/
  • https://developer.chrome.com/blog/inside-browser-part3/
レンダリングエンジンの工程
  • (図には書いていないけど)Parse
    • HTML と CSS をパース
    • DOM Tree と Style Rules を生成
  • JavaScript
    • 視覚的な操作を処理
  • Style
    • HTML 要素が、どの CSS ルールが割り当たるかを決定
    • DOM Tree と Style Rules を紐付けた Render Tree を生成
  • Layout
    • HTML 要素の位置と大きさを決定
    • Layout Tree を生成
    • Reflow とも呼ぶ
  • Paint
    • ブラウザに表示するピクセルを塗る
    • レイヤーを分ける
    • Draw とも呼ぶ
  • Composite
    • 正しい順序で、レイヤーを重ねていく
    • メインスレッドからコンポジットスレッド・ラスタースレッドに切り替わる
      • コンポジットスレッドから、ページを各タイルに分割して、ラスタースレッドに送る
      • ラスタースレッドは、ラスタライズして GPU に格納する

この工程が、実際に動いているところを見てみましょう。

DevTools でレンダリング工程を見てみる

次のシンプルな HTML を Chrome で開いてみましょう。

<div>Hello</div>

開いたページで DevTools を開き、Performance タブをクリックします。 左上にある reload ボタンを押して、計測してみましょう。

devtools_performance

計測の結果、 Main を見てみましょう。

devtools_performance_1

さきほど説明したレンダリングエンジンの工程(色も一致)が、見えると思います。

  • 青色 Parse HTML
  • 紫色 Recalculate Style
  • 紫色 Layout
  • (黄色は JavaScript 関係)
  • (緑色は Paint/Composite 関係)

視覚的に見やすい一方で、全体を網羅してみるのは難しいです。 そこで、 Event Log を開きます。

devtools_performance_2

レンダリングエンジンのイベントログが、色とともに表示されています。 ここには、さきほど見れなかった黄色や緑色のものもあります。

Tips: Performance タブに慣れよう

Performance タブには、様々な情報があります。

いきなりプロダクションリリースされているものに対して、Performance 計測すると、何を見たらよいかわからなくなります。

まずは、最小セットの HTML で見ていくと、情報量が絞られて、読みやすくなります。

また、計測の各場所には、工程の色が使われています。色も合わせて見ると、読みやすくなります。

ブラウザとリフレッシュレートと 60fps

ブラウザでアニメーションなど動きを出すときに、60fps を目標とすると良いです。

http://jankfree.org/ というサイトから引用します。

Modern browsers try to refresh the content on screen in sync with a device's refresh rate. For most devices today, the screen will refresh 60 times a second, or 60Hz. If there is some motion on screen (such as scrolling, transitions, or animations) a browser should create 60 frames per second to match the refresh rate.

ブラウザは、リフレッシュレートと同期してコンテンツを更新します。 最近のデバイスは、1 秒間に 60 回更新できるようです。そのため、ブラウザは 60fps で動作すべきと書いています。

DevTools から、fps を確認できます。 Rendering タブにある Frame Rendering Statsにチェックを入れます。

devtools_fps_1

そうすると、画面に次の画像が表示されます。

devtools_fps_2

今、ブラウザは 18.6 fps のようです。


fps が少ないと、どうなるんでしょうか。ジャンクと呼ばれる現象が発生します。

Jank is any stuttering, juddering or just plain halting that users see when a site or app isn't keeping up with the refresh rate. Jank is the result of frames taking too long for a browser to make, and it negatively impacts your users and how they experience your site or app.

リフレッシュレートに、画面が追いついていないと、ジャンクと呼ばれる滑らかではない動作になってしまいます。これは、ユーザーへの悪い体験をさせてしまいます。

https://googlechrome.github.io/devtools-samples/jank/ が、まさにそのジャンクの体験ができます。

レイアウトスラッシング

JavaScript や CSS を書いていると、DOM を追加してレイアウトが実行されたり、color を変えて、ペイントを実行されたりします。 レンダリングエンジンは、シングルスレッドで動いているため、レイアウトの実行やペイントの実行をしていると、他の工程が動作されません。

次のサイトにある JavaScript の関数を使うと、そのときのレイアウト情報を計算する必要があり、レイアウトが強制的に再計算されます。これがレイアウトスラッシングと呼ばれます。 レイアウトスラッシングは、FPS の低下につながります。

  • https://gist.github.com/paulirish/5d52fb081b3570c81e3a
    • 例えば、clientWidth

例を示しましょう。ボタン要素にスタイル変更し、clientWidth を参照したコードです。

<button>click</button>
<script>
  const b = document.querySelector("button");
  b.addEventListener("click", () => {
    b.setAttribute("style", `width: 100px;`);
    b.clientWidth;
  });
</script>

clientWidth を実行すると、そのときのレイアウト情報が必要になるため、強制的にレイアウトが実行されます。

layout_forced

強制レイアウトが発生しているのが、みてとれます。

b.clientWidth をコメントアウトすれば、Layout Forced は発生しません。 もっと、明らかに警告となるサンプルを用意しました。

<button id="btn">click</button>
<div id="root"></div>
<template id="template">
  <div style="position: relative">hello</div>
</template>
 
<script>
  const root = document.getElementById("root");
  const template = document.getElementById("template");
  [...Array(100)].forEach(() =>
    root.appendChild(template.content.cloneNode(true))
  );
 
  document.getElementById("btn").addEventListener("click", () => {
    setInterval(() => {
      document.querySelectorAll("div").forEach((el) => {
        el.style.left =
          (Math.sin(el.offsetTop + Date.now() / 1000) + 1) * 500 + "px";
      });
    }, 100);
  });
</script>

DevTools の Performance タブから見ると、forced reflow is likely a bottleneck と警告が出ているのが分かります。

devtools_warn_forced_reflow

対策としては、次があげられます。

  • レイアウトスラッシングを発生させる関数を実行しない、もしくはキャッシュする
  • Window.requestAnimationFrame() を利用する

参考までに

  • https://web.dev/avoid-large-complex-layouts-and-layout-thrashing/#avoid-forced-synchronous-layouts

DEMO は、次のページにもあります。

  • https://googlesamples.github.io/web-fundamentals/tools/chrome-devtools/rendering-tools/forcedsync.html

Paint と Composite

Paint もコストがかかります。そこで、Composite に任せることで、メインスレッドを開放し、パフォーマンスが良くなります。 具体的には、コンポジットで動作する transform や opasity とかがあります。

具体的な例を出しましょう。 次の例は、四角のボックスを左右に動かすサンプルです。 左右に動かす手段に、CSS の left のパターンと、transform のパターンを試してみます。

<style>
  @keyframes return {
    50% {
      left: 200px;
    }
    100% {
      left: 0px;
    }
    /* 50% {
      transform: translateX(200px);
    }
    100% {
      transform: translateX(0px);
    } */
  }
 
  .box {
    position: relative;
    width: 100px;
    height: 100px;
    left: 0px;
    border: 1px solid black;
  }
  .trans {
    animation-name: return;
    animation-duration: 2s;
    animation-iteration-count: infinite;
    animation-timing-function: ease;
  }
</style>
<div class="box trans"></div>

transform の場合は、left の部分をコメントアウトし、transform 部分をコメントアウトを外します。

このファイルをブラウザで開き、Performance タブで計測し、Event Log を確認します。

left の場合、layout,paint,composite が発生しています。

css_trigger_1

transform の場合、composite のみ発生しています。

css_trigger_2

このように、composite のみで動く CSS プロパティを選ぶと、軽量になります。 次のサイトには、CSS のどのプロパティがレイアウト・ペイント・コンポジットどれを更新するのか分かります。

  • https://csstriggers.com/

また、DevTools の Layers タブを開くと、ペイントのカウント回数やレイアウトが見れます。

left の場合の Layers は、次の画像です。 数秒経過しただけで、ペイントカウントが、数百を超えました。

devtools_layout_1

transform の場合の Layers は、次の画像です。 ペイントカウントが、たったの 2 回に留まりました。

devtools_layout_2

終わりに

レイアウトやペイントについて、調査をしていると、意図せずレイアウトやペイントを実行させていた人も、いるかもしれません。 パフォーマンスは、必要になったときにチューニングすればよいと思いますが、基本知識として本記事についての情報は、知っておいて損はないと思います。

参考

  • https://gist.github.com/paulirish/5d52fb081b3570c81e3a
  • https://dev.opera.com/articles/efficient-javascript/
ブラウザ
フロントエンド

-

読者になる

|

シェアする

|

silverbirders

silverbirder

Webソフトウェアエンジニア

ブログを応援する

この記事がよかったら、お布施という形で応援してもらえるとうれしいです。

おふせぼたん

※ ログイン不要で投稿できます。

※ 同じブラウザから投稿を削除できます。

0

読み込み中...

次の記事へ前の記事へ

関連する記事

タグ「ブラウザ」の記事

『Webブラウザエンジニアリング』第1~3章を読みました。

以下の書籍を1から3章まで読みました。 https://www.oreilly.co.jp/books/9784814401574/ 全部で16章もあるので、こまめに感想を残しておこうと思います。 Webの歴史 Web が成り立つ経緯につい

2026年03月20日

書籍レビュー
ブラウザ
iframeの難しさ

最近、iframeを使っています。 クライアントサイドで埋め込む想定で、iframeを使おうとしています。 色々と苦労したことがあったので、書いて残しておこうと思います。 レスポンスヘッダー 前提として、ウェブアプリケーションをプロダクショ

2026年02月18日

フロントエンド
ブラウザ
DuckDB WASMとOPFSでGoogleマイアクティビティをブラウザ完結で可視化してみた

DuckDB WASMとOrigin Private File System (OPFS) を組み合わせ、Google マイアクティビティの履歴をブラウザ内に閉じたまま扱えるようにしたときの設計と学びを整理しました。

2025年09月17日

開発ツール
ブラウザ

タグ「フロントエンド」の記事

Webフロントエンドのコードレビューメモ2

また、以下の記事の続きを書こうと思います。 https://silverbirder.github.io/blog/contents/20260312/ ビジネスロジック アプリケーションのビジネスロジックに関することについて書きます。 複

2026年03月18日

フロントエンド
Webフロントエンドのコードレビューメモ

Webフロントエンドのコードレビューをしているときに考えていることについて書きます。 毎日1記事投稿、1記事30分という制約を課していますので、本記事は完璧ではありません。(言い訳) また希望的な考えもあるので、実践していないものもあります

2026年03月12日

フロントエンド
AIの書いたコードの手直しを減らすお作法

AI にコードを書かせた後、余計なコードを見つけて消す作業があります。 不毛なことなので、それらの作業を減らすためのお作法を紹介します。 未使用コードを消す 以下でも書きましたが、未使用コードの検査に knip を使うことが多いです。 ht

2026年02月25日

AI
フロントエンド
← ブログ一覧へ