こちらの本です。例によって全部のテクニックは紹介できないので、ユニークビジョンで使えそうなやつだけかいつまんでの紹介になります。
基礎
まずは高速化のテクニックに入る前に、レンダリングの基礎的な話です。
レンダリングのフロー
Web ページのレンダリングは次の手順で進みます。
1. リソースの読み込み
2. Javascript の実行
3. レイアウトツリー構築
4. 描画
パフォーマンスの計測
レンダリングのどのフェイズで時間がかかっているかはブラウザの開発者ツールを使って確認するのが一般的です。
Javascript のいくつかの API を使って特定のフレームワークによらない計測を行うこともできます。
-
Navigation Timeing API
ナビゲーション時の詳細なパフォーマンス情報を取得できます。リソース読み込みの、DNS 名前解決や TLS トンネル確立などどの段階で時間がかかっていたかを取得できます。他にもリロードされたかブラウザバックされたかなどの詳細な値を取得できます。 -
User Timing API
任意の処理の時間を計測できます。
パフォーマンス解析ツール
Google Chrome には Audit という解析ツールがあります。他にも Google からは PageSpeed Insights や Lighthouse といった解析ツールが提供されています。
これによって、自動でパフォーマンスの悪い箇所を解析させることもできます。
RAIL
RAIL は、以下の 4 つの項目の頭文字です。
項目 | 基準時間 |
---|---|
Response | 100ms |
Animation | 16ms |
Idle | 50ms |
Load | 1000ms |
これは Google Chrome チームが考案したパフォーマンスモデルです。
入力に対する応答は 100ms 以内、アニメーションのフレーム間処理は 16ms 以内、Javascript によるアイドル時処理はそれぞれ 50ms 以内、ロードは 1000ms 以内に終了すべきというものです。
これらの数値は人間が遅延を知覚し始める目安の値になっています。
読み込みフェイズのチューニング
読み込みフェイズに時間がかかっている際のチューニングテクニックです。
CSS スプライト
CSS スプライトを使えば複数の画像を 1 つのリクエストで取得することができます。
これは、単純に複数の画像を 1 枚の画像にくっつけることで達成されます。
HTTP リクエストのオーバヘッドを減らすことができる点で有用です。
プリフェッチ
link
要素を使うとリソースの事前読み込み処理が行えます。
link
の rel
属性を設定すれば、「名前解決だけ事前に行う」「コネクションをあらかじめ確立しておく」「リソースをあらかじめ取得しておく」「レンダリングをあらかじめ行っておく」などの前処理を指定できます。
キャッシュ
ある期間を過ぎるまではユーザのデバイスからキャッシュが消えない強いキャッシュと、サーバからキャッシュクリアできる弱いキャッシュがあります。
強いキャッシュ
以下の 2 つのどちらかをヘッダに使うキャッシュの仕方です。
このキャッシュが効いた状態で強制的に新しい情報を取得させるにはクエリパラメータなどを使って別のリソースと解釈させるなどの方法があります。
- Expire
- Cache-Control
弱いキャッシュ
以下の 2 つのどちらかを使った条件付き HTTP リクエストによってキャッシュクリアできます。
更新が起こりやすいリソースのキャッシュに使うことができます。
- Last-Modified
- ETag
Service Worker
Service Worker はメインスレッドの Javascript とは別スレッドで動作し、リソース取得の際に必ず参照されるロジックを提供することができます。
あるリソースを取得する時にキャッシュから取得するかサーバから取得するかを動的に決定させられるようになります。また、この Service Worker はページとサーバの間にいるプロキシのように働き、サーバからのレスポンスを改変することもできます。
Javascript のチューニング
Javascript の実行フェイズに時間がかかっている際のチューニングテクニックです。
Web Workers
Web Worker API は通常、メインスレッド上でしか実行できない Javascript の処理を並列して別スレッドで実行させるための API です。
Web Worker のスレッドとメインスレッド間との通信は非同期のメッセージパッシングによって行われます。
Web Worker とメインスレッドの間ではデータの共有はできず、コピーがメッセージに載って渡されます。また、Web Worker は DOM 要素や document, window, parent オブジェクトを利用できないようになっています。
アイドル時処理
メインスレッドの Javascript を使えば DOM 要素などにアクセスしながらメインスレッドアイドル時にも処理を行うことができます。ただし、処理のチャンクを細切れにしないとメインスレッドをブロックしてしまうので、RAIL ではこれを 50ms 以内に抑えることを目標としています。
setTimeout
で 50ms ごとに処理を中断、発火を繰り返させればメインスレッドの処理を邪魔することなくアイドル時に計算が行えます。
ただし、これはスクロールなどの頻繁に発生する DOM イベントの発火を妨害する可能性があります。
レイアウトツリー構築のチューニング
レイアウトツリー構築フェイズに時間がかかっている際のチューニングテクニックです。
CSS セレクタ
CSS セレクタのマッチは直感とは異なり左から右に実行されます。
body > div.hoge {
...
}
例えば、このマッチは以下の順にチェックされ、チェックがどこかで通らなければその時点でマッチを諦めます。
-
class
属性にhoge
が含まれる - 要素が
div
である - 親が
body
である
つまり、基本は詳細にセレクタを書けば書くほど処理は遅くなることになります。この例であれば、class
が hoge
であることだけをセレクタにしておけばよかったかもしれません。
高度なセレクタ
子孫セレクタ、間接セレクタ、全称セレクタはマッチの計算時間を増大させます。要素探索のバックトラックや、探索要素数の増大のためです。
BEM
BEM は CSS の設計・命名規則の 1 つです。
-
Block
コンポーネントなど、ページのある要素のかたまり -
Element
Block 内の要素 -
Modifier
Block と Element の見た目や振る舞いの変更
各 Element のクラス名を次のように定義します。
class="block__element–-modifier"
基本的に単一のクラスセレクタで CSS の指定ができるためパフォーマンスが良いです。
レンダリング結果のチューニング
描画に時間がかかっている際のチューニングテクニックです。
translateZ
translateZ
は 3D 変形を行うための CSS 関数で、これを利用したレイヤは GPU で計算されます。
これを利用し、以下の記述によって GPU を利用したスピードアップを適用できます。
.hoge {
transform: translateZ(0);
}
VRAM への転送のオーバーヘッドと、このハックを使うレイヤがある限り VRAM 上にデータが保持されることを考えると気軽に使えるものではありません。再描画されることが少なく、領域もさほど大きくないものに対して、必要な時に絞って使用するのが肝要です。
おわり
アドカレ遅刻しました!