Web エンジニアの谷村です。最近の Web アプリケーションは、リッチな UI・機能とともに、サクサク操作できる UX = パフォーマンスが高いアプリケーションが求められていると思います。
パフォーマンスが高い = ユーザーが使いやすい・使ってくれるアプリケーションを実現していくためには、パフォーマンスチューニングは必須です。
実際の現場におけるパフォーマンスの課題は、個別具体性が高い場合が多く、課題に応じた解決手段を考えていく必要があると思います(API のレスポンス速度は問題ないが表示までが遅い・操作した時にもたついて感じる...etc)。
そういった課題を解決するために、まずは、そもそもブラウザがどういった流れで画面をレンダリングしているのかを知っておくことで、なぜその課題が発生しているのか、どういったアプローチが効果的なのか(なぜそのアプローチが効果的なのか)など考えやすくなると思いますので、簡単にですが、紹介します。
レンダリングの流れ
ブラウザでの描画は、クリティカルレンダリングパスと呼ばれる、下記フェーズが実行され、最終的にブラウザの画面に描画されます。
- Loading
- Scripting
- Rendering
- Layout
- Painting
パフォーマンスチューニングは、各フェーズの処理時間をどれだけ、最適化・高速化できるかにかかっています。そして、パフォーマンス上の課題は各フェーズで発生し、原因はそれぞれ異なります。各フェーズの処理を知っておくことで、原因の切り分けから、発生する課題の概要を掴みやすくなると思います。
ここから、各フェーズの主な処理を紹介します。
Loading(読み込み)
主な処理
- ネットワーク経由での HTML の読み込みと解析
- DOM・CSSOM ツリーの構築
- ネットワーク経由での各種リソースの読み込み
まず、ブラウザに入力された URL を元に HTTP リクエストを送信し、レスポンスとして HTML を取得します。取得した HTML をレンダリングエンジンが解析し、DOM(Document Obejct Model)ツリーを構築していきます。
DOM ツリーを構築する中で、リソース取得が必要な要素があった場合は、さらに読み込みを行い、レンダリングに必要なデータを集めていきます。
読み込みが発生する可能性がある要素とファイルの一例を下記に示します。
- link 要素
- CSS ファイル
- ファビコン
- フォント
- script 要素
- JavaScript ファイル
- img ・video 要素
- 画像ファイル
- 動画ファイル
script 要素は、DOM ツリーを変更する可能性があるため、後述する Scripting フェーズがこの段階で実行されます。その場合は HTML の解析も一度中断されます。※ 同期実行の script 要素のみ。
CSS がある場合は、こちらも解析され CSSOM(CSS Object Model)ツリーを構築します。
以降のフェーズでスタイル計算やレイアウトデータの作成のために使用されます。
発生しうる課題の一例
各種リソースの読み込み時間が長い
特に HTML、CSS、JavaScript ファイルはサイズが大きくなりがちで読み込み時間が長くなってしまいます。読み込み時間が長い分だけ描画完了やインタラクティブになる時間までへの影響がでてしまいます。
また、画像ファイルも無駄に大きな画像を取得していることが原因で、読み込み時間が増加してしまうといった課題もあります。
Scripting
主な処理
本フェーズは JavaScript の実行です。
Loading フェーズで取得もしくは、インラインで記述された JavaScript を実行します。
DOM ツリーの操作や非同期通信によるリソース読み込みなどが行われます。
JavaScript の実行は、他のレンダリング処理やユーザーの操作をブロックします。実行時間が長いとそれだけ画面描画・応答までの時間を遅延させてしまいます。
発生しうる課題の一例
同期的な JavaScript 実行によるレンダリングブロック
同期的な JavaScript の実行は、DOM ツリーの変更が発生しうるため、HTML の解析が中断されてしまい、実行完了まで script 要素の後続要素たちの DOM ツリーの構築やリソース取得が開始されなくなってしまいます。
Forced Synchronous Layout(強制レイアウト)による JavaScript 実行時間の増加
JavaScript の処理内でレイアウトに関する情報(要素の大きさ、位置情報など)を参照することで、Rendering・Layout の処理が本フェーズ中に必要になり、その分実行時間が増加してしまいます。
強制レイアウトを発生させるプロパティは What forces layout / reflow にまとまっていますので、参考にしてみてください。
Rendering
主な処理
DOM ツリーと CSSOM ツリーを元に、スタイルの計算を行い、レンダリングツリーを構築します。
スタイルの計算では、レンダリングエンジン内で CSSOM ツリーからルールセットを、DOM ツリーから DOM 要素を走査し、どの DOM 要素にどんな CSS プロパティが適用されるのかを計算します。
これは全ての DOM 要素に対して、総当たりで計算されます。
計算が完了し、各 DOM 要素に対して適用する CSS が確定すると、レンダリングツリーを構築します。
Layout
主な処理
レンダリングツリーを元に、ページ上のレイアウト情報の計算からレイアウトの決定を行います。
レイアウト情報の一例を下記に記載します。
- 要素のレイアウト(flex、align-items)
- 要素の幅、高さ、位置(width、height)
- 要素間のスペーシング(margin、padding)
- 要素の重ね合わせ(z-index)
発生しうる課題の一例
リフロー
リフローは、JavaScript などを通じて要素のレイアウト情報が変更され、画面表示を更新する必要がある時、再度 Rendering・Layout・Painting の処理が実行されることを指します。この時間がボトルネックとなり、ユーザー操作や描画のパフォーマンスに影響が出る場合があります。特に、レイアウト情報をアニメーションすると、描画コストが大きくなり、滑らかなアニメーションではなくなってしまいます。そういった場合は、リフローが発生しないプロパティで実現できないか検討するのが良いでしょう。
Painting
主な処理
レイアウトが決定されると、実際にピクセルを画面に描画していきます。
描画処理としては大きく三つに分かれています。
- ペイント(Paint)
- レンダリングツリーを元に、2D グラフィックエンジン向けの命令を作成します
- ラスタライズ(Rasterize)
- 生成された命令に基づいてピクセル(ビットマップ)へ描画します
- レイヤーという単位で一枚ずつ描画されます
- レイヤーの合成(Composite Layers)
- レイヤーを合成して、最終的に画面に描画する結果を作成します
- 基本 CPU で合成されますが、条件を満たすと GPU で合成される場合があります
ここまで完了すると、実際にユーザーがブラウザ上で描画された画面を見ることができるようになります。
再レンダリング
上記で説明した各フェーズは、JavaScript の実行(Scripting)に伴う DOM 要素の操作によって再度実行される可能性があります。
ただ、上記フェーズが全て実行されるとは限りません。必要ないフェーズは、ブラウザによってスキップされます。スキップされるかどうかは、操作内容によって制御することが可能です。
DOM 操作を行う際は、再実行されるフェーズを最小限にしつつ、実行時間も短くなるような処理を書くことが理想でしょう。
参考文献
参考サイト
developer.mozilla.org web.dev developer.mozilla.org developer.mozilla.org What forces layout/reflow. The comprehensive list. · GitHub