はじめに
『レンダリングの仕組みなんて知らなくても、ブラウザが勝手にやってくれるじゃん!』
当時駆け出しのエンジニアだった私はそう思っていました。
実際、当時の私はレンダリングの『レ』の字も知りませんでしたが、特に業務上で問題はありませんでした。
しかし、その時は突然訪れました。
クライアントの要望でアニメーションを多彩に取り入れた案件を実装した際に、テスト段階で一部ブラウザ(S○f○ri、E○ge)でアニメーションがひどい状況になっていることが発覚しました。
(開発中はChromeで確認を行っており、Chromeでは特に問題はなかったので発覚が遅れました。)
それからは、狂ったようにパフォーマンスの改善方法をググり、修正する日々が続きました。(最終的には、なんとかマルチブラウザでの動作も担保し、納品まで完了しました。)
その案件が落ち着いた後、改めて自分の調べたことを振り返ると、局所的な解決方法がすべてレンダリングの仕組みにつながっていることがわかりました。
『もっと早くレンダリングについて勉強していれば。。』
最近ではアニメーションライブラリが充実し、自分でアニメーションを書くことが少なくなりました。しかし、ユニークなアニメーションを実装する際や、何か問題が発生した時に解決する力を付けるためにも、レンダリングの仕組みを勉強することは大切だと今では感じています。
この記事はその反省の意も込めて、レンダリングの仕組みについてまとめたものです。
導入(パフォーマンスとフレームレート)
まずはパフォーマンスとフレームレートの関係についてご説明します。
スクロールがカクついたり、アニメーションがカクカクするのはなぜでしょうか?
これはフレームレートの低下が原因で起こります。フレームレートはFPSという単位で表され、ブラウザの場合は60fpsが最高となり、最もパフォーマンスの良い状態を表します。
60fpsということは、逆算すると~16.6msに1回の画面更新( = 1/60)を行っていることがわかります。
しかし、**Javascriptやレンダリングに重い処理があると、画面更新のタイミングまでに処理が間に合わず、フレームレートを下げてしまいます。**これが画面のカクつき、あるいはアニメーションの劣化に繋がります。
そのため、『パフォーマンスが悪いな』と感じた際はJavascriptを改善するか、レンダリングを改善する必要があります。
Javascriptを改善する場合は自分で記述したコードの負荷の重いところをDevtoolで見つけて改善すればよいのですが、**レンダリングはブラウザ側の処理でブラックボックス化されているため、基礎知識がないと修正がなかなか困難です。**こういった観点からもレンダリングの基礎知識は問題解決に大変有用です。
レンダリングの流れ
下の図はブラウザが各フレームごとに行っている処理の流れです。
例えば、画面の初期表示の際にはブラウザはこの処理フローを最初から行います。逆に画面の表示内容に更新があった場合は必要な工程のみを処理し、再計算が不要な工程は省略します。
ということは、どの変更がどのフローに関連しているのかを知っているだけで、ブラウザの負担を大きく減らすことができます。
##Parse(パース)
この工程ではHTMLとCSSの解析が行われます。ブラウザはHTMLとCSSを解析し、それぞれDOM Tree(DOMツリー)とStyle Rules(スタイルルールズ)という処理しやすい構造体に変換します。
Style Rulesの作成
##Style(スタイル)
この工程では先程作成したDOM TreeとStyle Rulesの紐づけが行われます。具体的にはどのスタイルがどの要素に適用されるのかをマッチングし、複数個のスタイルが一致する要素に関しては、スタイル適用の優先度に従って最終的に適用されるスタイルを割り出します。
ここでできた、スタイルと要素のマッチングはRender Tree(レンダーツリー)と呼ばれます。また、Render Treeの各要素はRenderer(レンダラー)と呼ばれます。
この工程はJavascriptでのDOM操作、またはCSSプロパティーの変更を行った際に基本的に呼ばれます。
##Layout(レイアウト)
この工程ではそれぞれの要素の位置と大きさの計算を行います。この処理はLayout(レイアウト)、またはReflow(リフロー)と呼ばれます。レイアウトの計算はRender Treeの上流から下流にかけて再帰的に行われ、位置と大きさの情報を考慮したLayout Tree(レイアウトツリー)が作成されます。
この工程はJavascriptにより位置、大きさが変更された場合、または以下の影響するプロパティーの変更があった場合に呼ばれます。
height, width, padding, margin, top, right, left, bottom, box-shadowなど
この工程は処理が重いため、なるべくこの工程に関わるプロパティーは変更しないようにする必要があります。(アニメーションも含めて)
Paint(ペイント)
Paint Records(ペイントレコード)の作成とLayer Tree(レイヤーツリー)の作成が行われます。
###Paint Recordsの作成
Paintの工程では画面上への描写指示の作成を行います。これがPaint Recordsと呼ばれます。Paint Recordsの順番は画面上での要素の重なり(z-index)を考慮します。
###Layer Treeの作成
同時にLayer Tree(レイヤーツリー)を作成します。**Layerの分離は変更の際の計算量を少なくするために大変有効です。**レイヤーを分離して他の要素を考慮しないでよい状態にしておくと、変更があった際に、そのレイヤーのみ再計算を行えばよいことになり、計算量を大幅に削減することができます。
**Layer Treeのレイヤーを分離する条件は先程の要素の重なり(z-index)とは関係がありません。**3Dトランスフォーム(transform: translateZ(0))やtransformを使用したアニメーションを検知した際にブラウザはLayerを分離します。また、**will-changeを使用した場合にもLayerは分けられます。**これがwill-changeのアニメーション高速化の理由です。
Composite(コンポジット)
前工程まではMain Threadで行われますが、この工程はMain Threadとは別のCompositor Thread(コンポジタースレッド)で処理されます。
ラスタライズと合成レイヤーの実行
まず先程作成した各レイヤーにラスタライズ処理(ビットマップ化)を行います。この処理は計算量が多いため、Raster Thread(ラスタースレッド)に代行されます。Raster Threadは合計4つ用意されており、平行して処理を行います。ラスタライズが完了したレイヤーは一つの合成レイヤーにまとめられ最終的にGPUに渡され画面に表示されます。
アニメーションによく利用されるtransform、opacityの適用もこの工程で行われます。そのためtransform, opacityの変更はレイアウトプロパティー(left,topなど)の変更に比べて、軽い処理となります。
transform, opacity
##レンダリングエンジンとプロパティー
このレンダリングの工程とプロパティーの関係はレンダリングエンジンによって変わってきます。例えば、**Chromeの採用しているBlinkでは、transformプロパティーはComposite処理のみに関係しますが、SafariのWebkitの場合、Layoutから関係してきます。**そのため、Chromeでは問題ないアニメーションがSafariの場合にはカクつく場合があります。
CSSプロパティーとレンダリングエンジンの関係はCSS Triggersで確認できます。
CSS Triggers
まとめ
レンダリングの工程を知ると、アニメーションに使用するべきCSSプロパティーがわかってきます。まず、レンダリングを快適にするにはLayoutの工程をなるべくさけ、Compositeのみで完結するtransform, opacityの利用が重要です。また、LayoutやPaintに関わる処理はwill-changeを適宜使いレイヤーを分けることによりパフォーマンスを向上しましょう。
あとがき
こちらの記事では一部内容を簡略化して説明させていただきました。もっと詳しく知りたいかたはブログで公開していますので、そちらの記事を御覧ください。
この記事のもっと詳しい説明
より詳細な説明をこちらに書きました。
フロントエンジニアなら知っておきたいブラウザレンダリングの仕組みをわかりやすく解説!
その他パフォーマンスについての説明
もっとパフォーマンスの改善について知りたい方はこちらを御覧ください。
- レンダリングのパフォーマンス対策入門〜アニメーション、JSで困った時に
- アニメーションにはtransformとopacityを使おう!理由をわかり易く説明!
- これって効いてる?will-changeが効いているか確認する方法
- requestAnimationFrameの仕組みと使い方!うまく使ってパフォーマンスを改善しよう!
- パフォーマンス低下の原因をChromeのDevtoolで調査しよう
ではまた
参考
How Browsers Work: Behind the scenes of modern web browsers
Inside look at modern web browser (part 3)
Rendering Performance