フロントエンドの現場にいると、「なぜかSafariだけレイアウトが崩れる」「スクロールがカクつく」といった、いわゆる「Safariの呪い」に遭遇することがあるだろう。
Chrome(Blink)が支配する現代において、WebKit(Safari)の挙動は時としてブラックボックスのように感じられるかもしれない。だが、WebKitのレンダリングパイプラインを理解することは、Appleデバイスの限られたリソースの中で、いかに「60fpsの滑らかさ」を維持するかという、フロントエンドの職人芸に直結する。
今日は、WebKitが裏側で何を考え、どう描画しているのか、その泥臭い現場の知見を共有しよう。
—
1. WebKitのレンダリングパイプライン:静かなる戦い
WebKitの描画プロセスは、単なる「HTMLを絵にする作業」ではない。それは、CPUとGPUの間で行われる、極めて効率的な「分業の最適化」だ。
1. Style/Layout: DOMツリーとCSSOMツリーをマージし、Render Treeを作る。ここで最も恐ろしいのが「リフロー(Reflow)」だ。要素のサイズや位置が変われば、親や兄弟要素まで影響が波及する。
2. Paint: 各要素の「色」「文字」「影」などを、小さな描画リスト(Display List)として記録する。
3. Composite: ここが重要だ。WebKitは、描画リストを「レイヤー」として切り分け、GPUに渡して合成する。これが「コンポジット(合成)」だ。
Safariが他のブラウザと決定的に違うのは、Appleデバイスのハードウェア(Metal API)との親和性だ。WebKitは、レイヤーをGPUに渡す際のオーバーヘッドを極限まで削っている。逆に言えば、「レイヤーを適当に増やしすぎると、かえってメモリを食い潰し、Safariのメモリ保護機能によってタブがクラッシュする」という特有の挙動がある。
—
2. なぜ「リフロー」は悪なのか?
現場でよくあるミスが、JavaScriptでDOMを操作する際に、読み取りと書き込みを交互に行うことだ。
// 悪い例:強制同期レイアウト(Layout Thrashing)
for (let i = 0; i < items.length; i++) {
// offsetHeightを読み取るために、ブラウザは強制的にリフローを実行する
const height = items[i].offsetHeight;
// 書き込みが発生するたびにリフローが走る
items[i].style.height = (height + 10) + 'px';
}
このコードは、ループの回数分だけ「計算→読み込み→計算→読み込み」を繰り返す。WebKitのエンジンは泣いている。これを防ぐには、「読み込み」と「書き込み」を完全に分離(Batching)するのが鉄則だ。
—
3. 実践:GPUレイヤーの魔術師になる
Safariでアニメーションがカクつくなら、GPUレイヤーの扱いを見直すべきだ。`will-change` プロパティは魔法の杖ではない。使いすぎればメモリ不足を招く。
以下のコードは、WebKitで滑らかなアニメーションを維持するための、最も安全で効率的なパターンだ。
/
- SafariでGPUアクセラレーションを適切に引き出すための最適化パターン
/
const box = document.querySelector(‘.target-element’);
// 1. will-changeは「必要な時だけ」付与する
// 常に貼りっぱなしはメモリの無駄。アニメーション直前に付与し、終了後に外すのがプロの技。
box.addEventListener(‘mouseenter’, () => {
box.style.willChange = ‘transform’;
});
box.addEventListener(‘transitionend’, () => {
// ブラウザに「もう最適化は不要だよ」と伝えてリソースを解放する
box.style.willChange = ‘auto’;
});
/
- 2. 賢いリペイントの回避
- 頻繁に変わる要素は、あえて「別レイヤー(独立したテクスチャ)」に追い出す。
- position: fixed や transform: translateZ(0) は強力だが、
- 多用するとWebKitのメモリ管理が悲鳴を上げる。
/
const optimizeAnimation = (element) => {
// transformによるアニメーションは、リペイント(Paint)をスキップできる。
// 合成(Composite)だけで完結するため、60fpsを叩き出しやすい。
element.style.transform = ‘translate3d(0, 0, 0)’;
element.style.backfaceVisibility = ‘hidden’; // GPU描画の安定化
};
—
4. シニアからのアドバイス:Safariと向き合うために
WebKitの最適化で最も重要なのは、「Developer ToolsのRenderingタブを信じすぎるな」ということだ。
Chromeで完璧でも、iOS SafariのWebViewではメモリ制限が非常に厳しい。特に「高解像度の画像」や「複雑なSVG」を大量にレイヤー化すると、Safariは即座にレンダリングを諦めるか、ページ全体をリロードさせる。
- 鉄則: レイアウトの変更(リフロー)は避けろ。
- 鉄則: 描画の変更(リペイント)は最小限にしろ。
- 鉄則: 合成(コンポジット)は「ここぞ」というアニメーションにだけ使え。
Webブラウザの仕組みを知ることは、単なる知識の蓄積ではない。ユーザーのデバイスに敬意を払い、最も快適な体験を届けるための「礼儀」だ。Safariのレンダリングパイプラインを掌握した時、君のフロントエンドエンジニアとしてのレベルは、また一段階上がっているはずだ。
次は、実際に開発中のプロダクトで `Performance` タブを開き、`Long Tasks` を削るところから始めてみないか?現場からは以上だ。

コメント