レンダリングの仕組みを知り、パフォーマンス向上に活かす

4 min read読了の目安(約4000字

この記事について

勉強会で作成したスライドの内容を簡単にまとめる。
メモレベルですが、どうかあしからず。
主な内容
・ブラウザのレンダリングの仕組みを把握する
・パフォーマンス向上に使えそうな小ネタの紹介

レンダリングの仕組み

大きく4つのプロセスに分けられる。

  • Loading
  • Scripting
  • Rendering
  • Painting

Loading

HTMLやレンダリングに必要なリソースを読み込む。
主な仕事はDownloadParsing

Download

HTMLを取得し、その中で参照されているリソースがあればそれらを読み込んでいく。

Parsing

リソースをレンダリングエンジンの内部リソースに変換する(HTML→DOM Tree、CSS→CSSOM Tree)
構造が複雑になる程、解析に要する時間が増える。

ボトルネックになりがちなところ

HTMLのパース中にscriptタグを発見すると、JSのダウンロードと実行が優先される。
それらが完了するまでレンダリングがブロックされるため、UXにも悪影響。
対策としては、async属性やdefer属性をscriptタグに付与すると良い。

Scripting

JSのコードをJavaScriptエンジンに引き渡し、実行する。

Rendering

レイアウトツリーを構築し、画面上にどのように表示されるか計算を行う。
主な仕事はCalculate StyleLayout

Calculate Style

DOM要素に対して、どのようなCSSプロパティが当たるのか計算する。
計算の順序は下記の通り。

  1. 全DOM要素にCSSルール内のセレクタがマッチするか総当たりで計算する
  2. それぞれのDOM要素にどのようなCSSが適用されるか判断する

Layout

視覚的なレイアウト情報を計算する。
・要素の大きさ
・要素のマージン
・要素のパディング
・要素の位置
・要素のZ軸の位置 など

何も考えずにインタラクション等を実装すると、Layoutの再計算を起こしがち。

Painting

レンダリングの結果を描画する。この段階で初めてピクセルが描画される。
主な仕事はPaintRasterizeComposite Layer

Paint

グラフィックエンジン向けの命令を生成する。

Rasterize

生成された命令に従ってレイヤーが生成され、ピクセルを描画する。
レイヤーが生成される条件は以下の通り。
・position:absolute
・position:fixed
・transform:translate3d等の、GPUで描画されるプロパティ
・opacity

Composite Layer

レイヤーを合成し、最終的なレンダリング結果を生成する。
通常はCPUによって合成されるが、3D系の処理が伴う場合はGPUが使用される。

パフォーマンスとレンダリングの関わり

JSの実行やドキュメント内のイベントによって、再レンダリングが引き起こされる。
JSでDOMを追加した場合はDOMの解析から、スタイルを操作した場合はLayoutやPaintからレンダリングをやり直すため、描画の際のコストが大きい。従って、

  1. いかに各プロセスを効率化するか
  2. 再レンダリングのコストをいかに減らすか

以上がパフォーマンス向上の肝となる。

パフォーマンス向上に使える小ネタ


CSSセレクタのマッチング処理を避ける

JSでスタイルを変更する場合、classを変更するとCSSセレクタのマッチング処理が走ってしまう。
そのため、パフォーマンスが求められる状況ならば、classではなくstyleを直接変更した方が良い。

// NG
div.classList.add(‘red’);
// OK
div.style.color = ‘red’;

しかし、styleを直接指定する方法だと保守性が著しく下がる。
早すぎる最適化は避け、その対応が本当に必要なのかどうか見極めが必要。


ドキュメントの再Layoutを避ける

1. Layoutの対象となる範囲を限定する

DOM操作等によって再度Layoutが引き起こされるとき、多くの場合ドキュメント全体が対象となる。
しかし、下記の条件を満たすことで、再Layoutの範囲を特定のDOM要素以下に限定できる。

  • SVG要素である
  • type=”text”もしくはtype=”search”のinput要素である
  • 次の全ての条件を満たす
    • displayがinlineやinline-blockではない
    • hightやwidthがauto以外の値に設定されている
    • heightが%単位を使っていない
    • overflowがscroll / auto / hidden
    • table要素の子孫ではない

2. DOMツリーから切り離して処理する

DOM操作を非同期で繰り返し行う際は、remove()でドキュメントのDOMツリーから対象を切り離す。
それにより、DOM操作に伴う全体の再Layoutを避けることができる。
操作後、必要になったタイミングで再度DOMツリー内に挿入すると良い。

3. CSS containを用いる

DOM要素にこのプロパティを指定すると、その要素がドキュメントのDOMツリーから独立した存在として扱われる。それにより、ドキュメント全体の再Layoutを避けることができる。

対応ブラウザ:chrome / firefox のみ

// layout,paint,styleを封じ込める
.target { contain: content;} 

// layout,paint,style,sizeを封じ込める
.target { contain: strict;}  

再レンダリングの工程を減らす

各要素のインタラクションを実装する場合、marginやposition、width / height によるアニメーションはLayoutPaintを引き起こす。それらのプロパティを避け、レンダリングの最終工程(Composite Layer)のみを引き起こすように実装すれば、レンダリングの工程が減るため描画コストも低くなる。

Composite Layerのみを引き起こすCSSプロパティ

・opacity
・transform


Composite Layer を最適化する

1. GPUによってレイヤーを合成する

CPUではなくGPUによってComposite Layerを処理することで、処理速度を上げることができる。

GPUレイヤーを生成する要素

・canvas
・transform
・CSS Filter

transform ハックによるGPUレイヤー生成

下記のスタイルを指定することで、表示に影響を与えずにGPUレイヤーを生成できる。

.target {transform:translateZ(0);}

無闇に用いると、GPUへの転送や描画に大きなコストがかかる
・Rasterizeされることが少ない要素に用いること
・領域がそれほど大きくない要素に用いること
・必要な時だけ用いること

2. will-changeを用いる

will-changeとは、translateハックの代替として使用可能なCSSプロパティ。
これを用いることで、レンダリングエンジンに対して、どのような変更が予定されているか伝えることができる。
レンダリングエンジンは、伝えられた内容に従って最適化を行える。

will-cahgeが働くCSSプロパティ

・opacity
・transform

その他、指定可能な要素

・scroll-position : アニメーションや素早いスクロールに対応
・contents : 要素内のコンテンツに対して何らかの変更やアニメーションが予想されると示す

単純なCSSによる指定は非推奨。常に最適化を強いるため、レンダリングエンジンの負担が大きくなる。
必要となった際にJSで動的に追加するのがお勧め。

最後に

テクニックは色々あるが、単にそれらを用いるだけだと「早すぎる最適化」になることも多々ある。
また、ブラウザのレンダリングの仕組みを理解せずにテクニックを用いるだけでは、逆効果になることも。
パフォーマンスの向上以前に、
・このタイミングで本当にチューニングが必要なのか
・そのテクニックの使いどころは正しいのか
以上を念頭に置くことが何よりも重要。