ブラウザのレンダリングエンジンにおけるレイアウトやペイントについて気になったので、調べました。 その内容をまとめます。レンダリングエンジンは、Chrome の Blink を題材とします。
レンダリングエンジンの処理工程は、次の記事が参考になります。

この工程が、実際に動いているところを見てみましょう。
次のシンプルな HTML を Chrome で開いてみましょう。
<div>Hello</div>開いたページで DevTools を開き、Performance タブをクリックします。 左上にある reload ボタンを押して、計測してみましょう。

計測の結果、 Main を見てみましょう。

さきほど説明したレンダリングエンジンの工程(色も一致)が、見えると思います。
Parse HTMLRecalculate StyleLayout視覚的に見やすい一方で、全体を網羅してみるのは難しいです。
そこで、 Event Log を開きます。

レンダリングエンジンのイベントログが、色とともに表示されています。 ここには、さきほど見れなかった黄色や緑色のものもあります。
Performance タブには、様々な情報があります。
いきなりプロダクションリリースされているものに対して、Performance 計測すると、何を見たらよいかわからなくなります。
まずは、最小セットの HTML で見ていくと、情報量が絞られて、読みやすくなります。
また、計測の各場所には、工程の色が使われています。色も合わせて見ると、読みやすくなります。
ブラウザでアニメーションなど動きを出すときに、60fps を目標とすると良いです。
http://jankfree.org/ というサイトから引用します。
Modern browsers try to refresh the content on screen in sync with a device's refresh rate. For most devices today, the screen will refresh 60 times a second, or 60Hz. If there is some motion on screen (such as scrolling, transitions, or animations) a browser should create 60 frames per second to match the refresh rate.
ブラウザは、リフレッシュレートと同期してコンテンツを更新します。 最近のデバイスは、1 秒間に 60 回更新できるようです。そのため、ブラウザは 60fps で動作すべきと書いています。
DevTools から、fps を確認できます。
Rendering タブにある Frame Rendering Statsにチェックを入れます。

そうすると、画面に次の画像が表示されます。

今、ブラウザは 18.6 fps のようです。
fps が少ないと、どうなるんでしょうか。ジャンクと呼ばれる現象が発生します。
Jank is any stuttering, juddering or just plain halting that users see when a site or app isn't keeping up with the refresh rate. Jank is the result of frames taking too long for a browser to make, and it negatively impacts your users and how they experience your site or app.
リフレッシュレートに、画面が追いついていないと、ジャンクと呼ばれる滑らかではない動作になってしまいます。これは、ユーザーへの悪い体験をさせてしまいます。
https://googlechrome.github.io/devtools-samples/jank/ が、まさにそのジャンクの体験ができます。
JavaScript や CSS を書いていると、DOM を追加してレイアウトが実行されたり、color を変えて、ペイントを実行されたりします。 レンダリングエンジンは、シングルスレッドで動いているため、レイアウトの実行やペイントの実行をしていると、他の工程が動作されません。
次のサイトにある JavaScript の関数を使うと、そのときのレイアウト情報を計算する必要があり、レイアウトが強制的に再計算されます。これがレイアウトスラッシングと呼ばれます。 レイアウトスラッシングは、FPS の低下につながります。
例を示しましょう。ボタン要素にスタイル変更し、clientWidth を参照したコードです。
<button>click</button>
<script>
const b = document.querySelector("button");
b.addEventListener("click", () => {
b.setAttribute("style", `width: 100px;`);
b.clientWidth;
});
</script>clientWidth を実行すると、そのときのレイアウト情報が必要になるため、強制的にレイアウトが実行されます。

強制レイアウトが発生しているのが、みてとれます。
b.clientWidth をコメントアウトすれば、Layout Forced は発生しません。
もっと、明らかに警告となるサンプルを用意しました。
<button id="btn">click</button>
<div id="root"></div>
<template id="template">
<div style="position: relative">hello</div>
</template>
<script>
const root = document.getElementById("root");
const template = document.getElementById("template");
[...Array(100)].forEach(() =>
root.appendChild(template.content.cloneNode(true))
);
document.getElementById("btn").addEventListener("click", () => {
setInterval(() => {
document.querySelectorAll("div").forEach((el) => {
el.style.left =
(Math.sin(el.offsetTop + Date.now() / 1000) + 1) * 500 + "px";
});
}, 100);
});
</script>DevTools の Performance タブから見ると、forced reflow is likely a bottleneck と警告が出ているのが分かります。

対策としては、次があげられます。
Window.requestAnimationFrame() を利用する参考までに
DEMO は、次のページにもあります。
Paint もコストがかかります。そこで、Composite に任せることで、メインスレッドを開放し、パフォーマンスが良くなります。 具体的には、コンポジットで動作する transform や opasity とかがあります。
具体的な例を出しましょう。 次の例は、四角のボックスを左右に動かすサンプルです。 左右に動かす手段に、CSS の left のパターンと、transform のパターンを試してみます。
<style>
@keyframes return {
50% {
left: 200px;
}
100% {
left: 0px;
}
/* 50% {
transform: translateX(200px);
}
100% {
transform: translateX(0px);
} */
}
.box {
position: relative;
width: 100px;
height: 100px;
left: 0px;
border: 1px solid black;
}
.trans {
animation-name: return;
animation-duration: 2s;
animation-iteration-count: infinite;
animation-timing-function: ease;
}
</style>
<div class="box trans"></div>transform の場合は、left の部分をコメントアウトし、transform 部分をコメントアウトを外します。
このファイルをブラウザで開き、Performance タブで計測し、Event Log を確認します。
left の場合、layout,paint,composite が発生しています。

transform の場合、composite のみ発生しています。

このように、composite のみで動く CSS プロパティを選ぶと、軽量になります。 次のサイトには、CSS のどのプロパティがレイアウト・ペイント・コンポジットどれを更新するのか分かります。
また、DevTools の Layers タブを開くと、ペイントのカウント回数やレイアウトが見れます。
left の場合の Layers は、次の画像です。 数秒経過しただけで、ペイントカウントが、数百を超えました。

transform の場合の Layers は、次の画像です。 ペイントカウントが、たったの 2 回に留まりました。

レイアウトやペイントについて、調査をしていると、意図せずレイアウトやペイントを実行させていた人も、いるかもしれません。 パフォーマンスは、必要になったときにチューニングすればよいと思いますが、基本知識として本記事についての情報は、知っておいて損はないと思います。
-
タグ「ブラウザ」の記事
DuckDB WASMとOrigin Private File System (OPFS) を組み合わせ、Google マイアクティビティの履歴をブラウザ内に閉じたまま扱えるようにしたときの設計と学びを整理しました。
こんにちは、@silverbirderです。最近、湖県に移住してWebフロントエンドのお仕事をしています。お仕事をしていると、ユーザー体験を良くするためには、大きな改善をせずとも小さな改善だけでも十分な効果があると思い始めました。本記事では、その小さな改善となる、3つのことについて書きたいと思います。
ブログ記事のOGP画像に、ブログタイトルを入れたい場面があります。その際、タイトルが長い場合は複数行に分けたり、省略したりする必要があります。今回は、試してみてよさそうだった2つの方法を紹介します。
2025-02-06
タグ「フロントエンド」の記事
最近、ヒューマンインターフェース ガイドライン(HIG)という言葉を知りました。 「ヒューマンインターフェイスガイドライン」には、どのAppleプラットフォームでも優れた体験を設計できるようにするためのガイドとベストプラクティスが含まれてい
2026-01-24
主にWeb関連の個人開発をしている際に心がけていることを書きます。 月末に近づくにつれ、AIの利用上限に達してしまうことがあります。 その状況になった時、以下のいずれかの選択肢が私の中では残っています。 課金して利用上限を増やす 無料モデル
個人サイトをリニューアルをしています。 ノート風のデザインを目指して、スタイルを調整していました。 ノートの見た目は、現実にあるノートを再現しようとCSSを書いていました。 現在、以下の画像のようなノートになっています。 ノート風デザインの
2026-01-20