- Authors

- Name
- Youngju Kim
- @fjvbn20031
- はじめに
- 1. URL入力(にゅうりょく)から画面表示(がめんひょうじ)まで(面接(めんせつ)完璧(かんぺき)回答(かいとう))
- 2. ネットワーク段階(だんかい)
- 3. HTMLパース
- 4. CSSパース
- 5. レンダーツリー
- 6. レイアウト(Reflow)
- 7. ペイントと合成(ごうせい)
- 8. Critical Rendering Path最適化(さいてきか)
- 9. リフロー vs リペイント
- 10. イベントループとタスクキュー
- 11. Web WorkerとOffscreenCanvas
- 12. DevTools Performanceパネルガイド
- 13. 面接質問(めんせつしつもん)とクイズ
- 参考資料(さんこうしりょう)
はじめに
「ブラウザのアドレスバーにURLを入力(にゅうりょく)すると何(なに)が起(お)こりますか?」
この質問(しつもん)はフロントエンド面接(めんせつ)で最(もっと)も頻繁(ひんぱん)に出(で)る質問(しつもん)の一(ひと)つであり、ブラウザの動作原理(どうさげんり)に対(たい)する深(ふか)い理解(りかい)を確認(かくにん)する核心的(かくしんてき)な質問(しつもん)です。単純(たんじゅん)に「HTMLを受(う)け取(と)って画面(がめん)に表示(ひょうじ)する」という回答(かいとう)では不十分(ふじゅうぶん)です。
この記事(きじ)では、DNS検索(けんさく)からTCP接続(せつぞく)、HTML/CSSパース、DOM/CSSOM構築(こうちく)、レンダーツリー生成(せいせい)、レイアウト、ペイント、合成(ごうせい)、そしてイベントループまで、ブラウザ動作(どうさ)のすべての段階(だんかい)を体系的(たいけいてき)に解説(かいせつ)します。
1. URL入力(にゅうりょく)から画面表示(がめんひょうじ)まで(面接(めんせつ)完璧(かんぺき)回答(かいとう))
1.1 全体(ぜんたい)フローの要約(ようやく)
ユーザー:URL入力 + Enter
|
v
1. URLパース(プロトコル、ドメイン、パス分離)
|
v
2. DNS検索(ドメイン → IPアドレス)
|
v
3. TCP接続(3-way handshake)
|
v
4. TLSハンドシェイク(HTTPSの場合)
|
v
5. HTTPリクエスト/レスポンス
|
v
6. HTMLパース → DOMツリー構築
|
v
7. CSSパース → CSSOMツリー構築
|
v
8. DOM + CSSOM → レンダーツリー生成
|
v
9. レイアウト(各要素の位置/サイズ計算)
|
v
10. ペイント(ピクセルで描画)
|
v
11. 合成(レイヤー合成、GPU)
|
v
画面に表示!
2. ネットワーク段階(だんかい)
2.1 DNS検索(けんさく)(Domain Name System)
ブラウザがドメイン名(めい)をIPアドレスに変換(へんかん)するプロセスです。
検索順序:
1. ブラウザDNSキャッシュ確認
2. OS DNSキャッシュ確認
3. ローカルhostsファイル確認
4. DNSリゾルバ(ISP)検索
5. ルートDNSサーバー
6. TLD DNSサーバー(.com、.jpなど)
7. 権威DNSサーバー(実際のIPを返却)
例:www.example.com の検索
Browser Cache → ミス
OS Cache → ミス
Router Cache → ミス
ISP DNS Resolver → ミス
Root Server → ".comはこちらに聞いて"
.com TLD Server → "example.comはこちらに聞いて"
example.com NS → "IPは93.184.216.34"
最適化(さいてきか)テクニック:
dns-prefetch: DNSを事前(じぜん)に解決(かいけつ)
<link rel="dns-prefetch" href="//api.example.com" />
<link rel="preconnect" href="https://cdn.example.com" />
2.2 TCP 3-Way Handshake
Client Server
| |
|--- SYN (seq=x) ------->|
| |
|<-- SYN-ACK (seq=y, -----|
| ack=x+1) |
| |
|--- ACK (ack=y+1) ----->|
| |
接続完了!
2.3 TLSハンドシェイク
HTTPS接続時(せつぞくじ)に追加(ついか)される暗号化(あんごうか)プロセスです。
Client Server
| |
|-- ClientHello (TLSバージョン,-->|
| 暗号スイートリスト) |
| |
|<-- ServerHello, 証明書, ---|
| サーバー鍵交換 |
| |
|-- クライアント鍵交換, -->|
| ChangeCipherSpec, |
| Finished |
| |
|<-- ChangeCipherSpec, ---|
| Finished |
| |
暗号化通信開始!
2.4 HTTPリクエスト/レスポンス
GET / HTTP/2
Host: www.example.com
Accept: text/html,application/xhtml+xml
Accept-Encoding: gzip, br
Connection: keep-alive
HTTP/2 200 OK
Content-Type: text/html; charset=UTF-8
Content-Encoding: gzip
Cache-Control: max-age=3600
Content-Length: 12345
<!DOCTYPE html>
<html>...
3. HTMLパース
3.1 トークナイザとツリー構築(こうちく)
HTMLパーサはバイトストリームをトークンに変換(へんかん)し、トークンをDOMノードに構築(こうちく)します。
バイト → 文字 → トークン → ノード → DOMツリー
例のHTML:
<html>
<head>
<title>My Page</title>
</head>
<body>
<h1>Hello</h1>
<p>World</p>
</body>
</html>
DOMツリー:
Document
└─ html
├─ head
│ └─ title
│ └─ "My Page"
└─ body
├─ h1
│ └─ "Hello"
└─ p
└─ "World"
3.2 スクリプトブロッキング(Parser Blocking)
デフォルトでは、scriptタグに遭遇(そうぐう)するとHTMLパースが中断(ちゅうだん)されます。
<!-- パーサーブロッキング:HTMLパース停止、スクリプトDL+実行後に再開 -->
<script src="app.js"></script>
<!-- defer:HTMLパースと並行DL、DOM完成後に実行 -->
<script defer src="app.js"></script>
<!-- async:HTMLパースと並行DL、DL完了時に即実行 -->
<script async src="analytics.js"></script>
defer vs async 比較(ひかく):
通常のscript:
HTML: ───████████████|████|████████
JS: |DL |実行|
defer:
HTML: ───████████████████████████|実行|
JS: |████ ダウンロード ████|
async:
HTML: ───████████|実行|████████████
JS: |████DL|
defer: 順序(じゅんじょ)保証(ほしょう)、DOMContentLoaded前(まえ)に実行(じっこう)async: 順序(じゅんじょ)保証(ほしょう)なし、ダウンロード完了(かんりょう)即時(そくじ)実行(じっこう)
3.3 プリロードスキャナ
HTMLパーサがブロックされていても、プリロードスキャナは動作(どうさ)を続(つづ)け、リソースを事前(じぜん)に発見(はっけん)します。
<!-- preload:重要リソースの事前ロード -->
<link rel="preload" href="critical.css" as="style" />
<link rel="preload" href="hero.webp" as="image" />
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin />
<!-- prefetch:次のナビゲーション用リソースを事前取得 -->
<link rel="prefetch" href="/next-page.html" />
4. CSSパース
4.1 CSSOM構築(こうちく)
CSSもHTMLと類似(るいじ)のプロセスを経(へ)ます。
バイト → 文字 → トークン → ノード → CSSOMツリー
CSS:
body { font-size: 16px; }
h1 { color: blue; font-size: 2em; }
p { color: #333; }
CSSOMツリー:
body (font-size: 16px)
├─ h1 (color: blue, font-size: 32px)
└─ p (color: #333)
4.2 CSSはレンダーブロッキングリソース
CSSがロードされる前(まえ)にはレンダーツリーを構築(こうちく)できないため、CSSはレンダーブロッキングリソースです。
<!-- レンダーブロッキング:このCSSがロード/パースされるまでレンダリング不可 -->
<link rel="stylesheet" href="styles.css" />
<!-- 条件付き:印刷用はレンダーブロックしない -->
<link rel="stylesheet" href="print.css" media="print" />
<!-- 条件付き:特定ビューポートでのみレンダーブロック -->
<link rel="stylesheet" href="mobile.css" media="(max-width: 768px)" />
4.3 Specificity(詳細度(しょうさいど))計算(けいさん)
!important > インラインスタイル > ID > クラス/属性/擬似クラス > タグ/擬似要素
計算法:(a, b, c)
- a:IDセレクタの数
- b:クラス、属性、擬似クラスの数
- c:タグ、擬似要素の数
例:
#header .nav a → (1, 1, 1)
.nav .item.active → (0, 3, 0)
nav ul li a → (0, 0, 4)
#main #content p.text → (2, 1, 1)
4.4 カスケードルール
優先順位(高い順):
1. !important付きユーザーエージェントスタイル
2. !important付きユーザースタイル
3. !important付き作成者スタイル
4. 作成者スタイル(詳細度順)
5. ユーザースタイル
6. ユーザーエージェントスタイル(ブラウザデフォルト)
5. レンダーツリー
5.1 DOM + CSSOM = レンダーツリー
DOMツリー: CSSOM:
html body { font: 16px }
├─ head h1 { color: blue }
│ └─ title p { color: #333 }
├─ body .hidden { display: none }
│ ├─ h1
│ ├─ p
│ └─ div.hidden
│ └─ span (visibility: hidden)
レンダーツリー(可視要素のみ):
html
└─ body (font: 16px)
├─ h1 (color: blue)
├─ p (color: #333)
└─ span (visibility: hidden) ← 含まれる!スペースを占有
[div.hiddenは除外 - display: none]
5.2 display: none vs visibility: hidden
| プロパティ | レンダーツリーに含(ふく)まれる | スペース占有(せんゆう) | リフロー発生(はっせい) |
|---|---|---|---|
display: none | いいえ | いいえ | 追加/削除時に発生 |
visibility: hidden | はい | はい | いいえ |
opacity: 0 | はい | はい | いいえ |
5.3 レンダーツリー構築(こうちく)プロセス
- DOMツリーのルートから走査(そうさ)
- 非可視(ひかし)ノードをスキップ(script、meta、display: none)
- 各(かく)可視(かし)ノードにCSSOMルールをマッチング
- 計算(けいさん)されたスタイルと共(とも)にレンダーツリーに追加(ついか)
6. レイアウト(Reflow)
6.1 ボックスモデル
すべての要素(ようそ)はボックスモデルで表現(ひょうげん)されます。
┌─────────────────────────────────────┐
│ margin │
│ ┌───────────────────────────────┐ │
│ │ border │ │
│ │ ┌───────────────────────┐ │ │
│ │ │ padding │ │ │
│ │ │ ┌─────────────────┐ │ │ │
│ │ │ │ content │ │ │ │
│ │ │ │ (width x height)│ │ │ │
│ │ │ └─────────────────┘ │ │ │
│ │ └───────────────────────┘ │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
box-sizing: content-box(デフォルト)
width = コンテンツ幅のみ
実際の幅 = width + padding + border
box-sizing: border-box(推奨)
width = コンテンツ + padding + border
実際の幅 = width
6.2 Block vs Inline
Block要素(div、p、h1...):
┌──────────────────────────────┐
│ Block 1(全幅を占有) │
├──────────────────────────────┤
│ Block 2 │
├──────────────────────────────┤
│ Block 3 │
└──────────────────────────────┘
Inline要素(span、a、strong...):
[inline1][inline2][inline3]
[inline4が長くて次の行へ...]
Inline-block:
[inline-block1] [inline-block2]
(ブロックのように幅/高さ設定可能、インラインのように並ぶ)
6.3 レイアウトプロセス
- ルートから開始(かいし): ビューポートサイズを決定(けってい)
- ブロックレイアウト: 上(うえ)から下(した)にブロックを配置(はいち)
- インラインレイアウト: 左(ひだり)から右(みぎ)にインラインを配置(はいち)
- Float処理(しょり): テキストフローを調整(ちょうせい)
- 位置計算(いちけいさん): relative、absolute、fixed、stickyを処理(しょり)
- サイズ確定(かくてい): すべての要素(ようそ)の最終(さいしゅう)位置(いち)とサイズを計算(けいさん)
7. ペイントと合成(ごうせい)
7.1 ペイント(Paint)
レイアウトが完了(かんりょう)すると、各要素(かくようそ)を実際(じっさい)のピクセルで描画(びょうが)します。
ペイント順序(z-order):
1. 背景色
2. 背景画像
3. ボーダー
4. 子要素
5. アウトライン
ペイントは複数のレイヤーで実行されます。
7.2 レイヤー生成(せいせい)条件(じょうけん)
以下(いか)の条件(じょうけん)で新(あたら)しい合成(ごうせい)レイヤーが生成(せいせい)されます:
transform: translate3d()またはtranslateZ()will-change: transformまたはwill-change: opacityposition: fixed- CSS アニメーション/トランジションがopacity、transformに適用(てきよう)
- ビデオ、キャンバス要素(ようそ)
filterプロパティ使用(しよう)
/* 合成レイヤーを強制生成 */
.layer {
will-change: transform;
/* または */
transform: translateZ(0);
}
7.3 合成(ごうせい)(Compositing)
GPUが各(かく)レイヤーを最終的(さいしゅうてき)に合(あ)わせる段階(だんかい)です。
合成ステップ:
1. レイヤーに分割(Layer Tree生成)
2. 各レイヤーをタイルに分割
3. タイルをラスタライズ(GPUでビットマップに変換)
4. ドロークワッド生成(画面位置情報)
5. コンポジターフレーム生成(最終合成)
6. GPUで画面に表示
レンダリングパイプライン比較:
JS/CSS変更
→ Style → Layout → Paint → Composite (フルパイプライン)
→ Style → Paint → Composite (Layoutスキップ)
→ Style → Composite (最速!)
7.4 GPUアクセラレーションプロパティ
/* 合成のみ発生するプロパティ(最速) */
.fast {
transform: translate(10px, 20px); /* 位置移動 */
transform: scale(1.2); /* サイズ変更 */
transform: rotate(45deg); /* 回転 */
opacity: 0.5; /* 透明度 */
}
/* ペイント発生(中間) */
.medium {
color: red;
background-color: blue;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
}
/* レイアウト発生(最遅) */
.slow {
width: 200px;
height: 100px;
margin: 10px;
padding: 20px;
font-size: 18px;
}
8. Critical Rendering Path最適化(さいてきか)
8.1 レンダーブロッキングリソースの排除(はいじょ)
<!-- CSS:Critical CSSのインライン化 -->
<style>
/* 最初の画面に必要な最小CSSのみインライン */
body { margin: 0; font-family: system-ui; }
.hero { background: #3b82f6; color: white; padding: 2rem; }
</style>
<!-- 残りのCSSは非同期ロード -->
<link rel="preload" href="styles.css" as="style"
onload="this.onload=null;this.rel='stylesheet'" />
<noscript><link rel="stylesheet" href="styles.css" /></noscript>
<!-- JS:パーサーブロッキング防止 -->
<script defer src="app.js"></script>
8.2 リソースヒント
<!-- DNS事前検索 -->
<link rel="dns-prefetch" href="//api.example.com" />
<!-- 事前接続(DNS + TCP + TLS) -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<!-- 重要リソースの事前ロード -->
<link rel="preload" href="hero.webp" as="image" />
<link rel="preload" href="critical.js" as="script" />
<!-- 次のナビゲーションリソースの先読み -->
<link rel="prefetch" href="/about" />
<!-- 次のページ全体の事前レンダリング -->
<link rel="prerender" href="/likely-next-page" />
8.3 最適化(さいてきか)チェックリスト
- CSSをheadに配置(はいち): レンダーブロッキングを最小化(さいしょうか)
- Critical CSSのインライン化: 最初(さいしょ)の画面(がめん)に必要(ひつよう)なCSSのみ
- JSにdefer/asyncを使用(しよう): パーサーブロッキング防止(ぼうし)
- リソース圧縮(あっしゅく): gzip/brotli活用(かつよう)
- 画像最適化(がぞうさいてきか): WebP/AVIF、lazy loading、srcset
- フォント最適化(さいてきか): font-display: swap、preload
- HTTP/2使用(しよう): マルチプレクシングで並列(へいれつ)リクエスト
9. リフロー vs リペイント
9.1 リフロー(Reflow / Layout)
レイアウトを再計算(さいけいさん)するコストの大(おお)きい作業(さぎょう)です。
リフロートリガー:
- 要素(ようそ)の追加(ついか)/削除(さくじょ)
- 要素(ようそ)サイズ変更(へんこう)(width、height、padding、margin、border)
- フォントサイズ変更(へんこう)
- テキスト内容(ないよう)変更(へんこう)
- ウィンドウリサイズ
- 計算(けいさん)されたスタイルの読(よ)み取(と)り(offsetWidth、getComputedStyleなど)
9.2 リペイント(Repaint)
視覚的(しかくてき)なプロパティのみ変更(へんこう)される比較的(ひかくてき)安価(あんか)な作業(さぎょう)です。
リペイントのみトリガー(リフローなし):
- color、background-color
- visibility
- box-shadow
- outline
9.3 DOM読(よ)み/書(か)きバッチング
// 悪い例:読み書きが交差して強制リフロー発生
const items = document.querySelectorAll('.item');
items.forEach(item => {
const width = item.offsetWidth; // 読み取り → 強制リフロー!
item.style.width = width + 10 + 'px'; // 書き込み
});
// 良い例:読みを先に、書きを後に(バッチング)
const widths = [];
items.forEach(item => {
widths.push(item.offsetWidth); // すべての読みを先に
});
items.forEach((item, i) => {
item.style.width = widths[i] + 10 + 'px'; // すべての書きを後に
});
9.4 Virtual DOMが存在(そんざい)する理由(りゆう)
直接DOM操作:
変更1 → リフロー → リペイント
変更2 → リフロー → リペイント
変更3 → リフロー → リペイント
Virtual DOM(React):
変更1、2、3をメモリで計算(diff)
→ 最小限の実際DOM変更
→ リフロー1回 → リペイント1回
10. イベントループとタスクキュー
10.1 JavaScriptランタイム構造(こうぞう)
┌─────────────────────────┐
│ Call Stack │
│ (関数実行コンテキスト) │
└───────────┬─────────────┘
│
┌───────────▼─────────────┐
│ Event Loop │
│ (スタックが空ならキュー確認)│
└───┬─────────────────┬───┘
│ │
┌───▼───┐ ┌────▼────┐
│Micro │ │ Macro │
│Task │ │ Task │
│Queue │ │ Queue │
└───────┘ └─────────┘
10.2 Macrotask vs Microtask
console.log('1: 同期');
setTimeout(() => {
console.log('2: Macrotask (setTimeout)');
}, 0);
Promise.resolve().then(() => {
console.log('3: Microtask (Promise)');
});
queueMicrotask(() => {
console.log('4: Microtask (queueMicrotask)');
});
console.log('5: 同期');
// 出力順序:
// 1: 同期
// 5: 同期
// 3: Microtask (Promise)
// 4: Microtask (queueMicrotask)
// 2: Macrotask (setTimeout)
Macrotask(Task Queue):
- setTimeout、setInterval
- I/Oコールバック
- UIレンダリング
- requestAnimationFrame
Microtask(Microtask Queue):
- Promise.then/catch/finally
- queueMicrotask
- MutationObserver
10.3 イベントループ実行(じっこう)順序(じゅんじょ)
1. Call Stackのすべての同期コードを実行
2. Call Stackが空になったら:
a. Microtask Queueのすべてのタスクを実行(キューが空になるまで)
b. レンダリングが必要ならレンダリングを実行
c. Macrotask Queueから1つのタスクを取り出して実行
3. 2に戻って繰り返し
10.4 requestAnimationFrame (rAF)
// rAF:次のリペイント直前にコールバックを実行
// 通常60fps = 約16.67ms間隔
function animate() {
// アニメーションロジック
element.style.transform = `translateX(${position}px)`;
position += 2;
if (position < 500) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
rAF vs setTimeout:
// 悪い例:setTimeoutでアニメーション
// フレーム落ち、不安定な間隔
setInterval(() => {
moveElement();
}, 16);
// 良い例:rAF使用
// ブラウザリフレッシュと同期、スムーズな60fps
function animate() {
moveElement();
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
10.5 requestIdleCallback
// ブラウザがアイドル状態のときに実行
// 重要でないタスクに使用
requestIdleCallback((deadline) => {
// deadline.timeRemaining()で残り時間を確認
while (deadline.timeRemaining() > 0 && tasks.length > 0) {
performTask(tasks.shift());
}
if (tasks.length > 0) {
requestIdleCallback(processRemainingTasks);
}
});
11. Web WorkerとOffscreenCanvas
11.1 Web Worker
メインスレッドをブロックせずにバックグラウンドでJavaScriptを実行(じっこう)します。
// main.js
const worker = new Worker('worker.js');
worker.postMessage({ type: 'HEAVY_CALC', data: bigArray });
worker.onmessage = (event) => {
console.log('結果:', event.data.result);
};
// worker.js
self.onmessage = (event) => {
if (event.data.type === 'HEAVY_CALC') {
const result = heavyComputation(event.data.data);
self.postMessage({ result });
}
};
function heavyComputation(data) {
// CPU集約的な作業(ソート、暗号化、画像処理など)
return data.sort((a, b) => a - b);
}
11.2 Workerの種類(しゅるい)
// 1. Dedicated Worker:1対1の関係
const dedicated = new Worker('worker.js');
// 2. Shared Worker:複数のタブ/iframeで共有
const shared = new SharedWorker('shared.js');
shared.port.start();
shared.port.postMessage('hello');
// 3. Service Worker:ネットワークプロキシ、オフラインサポート
navigator.serviceWorker.register('/sw.js');
11.3 OffscreenCanvas
Workerでキャンバスレンダリングを実行(じっこう)します。
// main.js
const canvas = document.getElementById('canvas');
const offscreen = canvas.transferControlToOffscreen();
const worker = new Worker('render-worker.js');
worker.postMessage({ canvas: offscreen }, [offscreen]);
// render-worker.js
self.onmessage = (event) => {
const canvas = event.data.canvas;
const ctx = canvas.getContext('2d');
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 複雑なレンダリングロジック...
ctx.fillStyle = '#3b82f6';
ctx.fillRect(x, y, 50, 50);
requestAnimationFrame(draw);
}
draw();
};
12. DevTools Performanceパネルガイド
12.1 Performanceタブの使(つか)い方(かた)
1. DevToolsを開く(F12またはCmd+Option+I)
2. Performanceタブを選択
3. 録画開始(Ctrl+E)
4. ページを操作
5. 録画停止
結果の解釈:
- FPSチャート:緑が60fps、赤がフレームドロップ
- CPUチャート:色別の作業種類
- 黄色:JavaScript実行
- 紫:レイアウト(リフロー)
- 緑:ペイント
- グレー:その他
12.2 主要(しゅよう)メトリクス
Core Web Vitals:
- LCP(Largest Contentful Paint):2.5秒以内 → 良好
- INP(Interaction to Next Paint):200ms以内 → 良好
- CLS(Cumulative Layout Shift):0.1以下 → 良好
追加メトリクス:
- FCP(First Contentful Paint):最初のコンテンツ表示
- TTFB(Time to First Byte):サーバー応答時間
- TBT(Total Blocking Time):メインスレッドブロック時間
12.3 一般的(いっぱんてき)なパフォーマンス問題(もんだい)パターン
1. ロングタスク(Long Task):
- 50ms以上メインスレッドをブロック
- 解決:コード分割、Web Worker活用
2. レイアウトトラッシング(Layout Thrashing):
- 読み書き交差で強制リフローの繰り返し
- 解決:読み/書きバッチング
3. 過剰なペイント:
- 不必要な領域の再ペイント
- 解決:will-change、transformを使用
4. 大きなDOMツリー:
- 1500+ノードはパフォーマンス低下
- 解決:仮想化(react-virtualized、tanstack-virtual)
13. 面接質問(めんせつしつもん)とクイズ
面接質問(めんせつしつもん)10選(せん)
Q1: ブラウザのアドレスバーにURLを入力(にゅうりょく)すると何(なに)が起(お)こりますか?
URLパース後(ご)、DNS検索(けんさく)でIPを取得(しゅとく)し、TCP 3-way handshakeで接続(せつぞく)します。HTTPSならTLSハンドシェイクも実行(じっこう)します。HTTPレスポンスでHTMLを受信(じゅしん)するとパーサがDOMツリーを構築(こうちく)し、CSSをパースしてCSSOMを作成(さくせい)します。DOMとCSSOMを結合(けつごう)してレンダーツリーを生成(せいせい)し、レイアウトで位置(いち)/サイズを計算(けいさん)し、ペイントでピクセルを描画(びょうが)し、合成(ごうせい)でGPUが最終画面(さいしゅうがめん)を表示(ひょうじ)します。
Q2: Critical Rendering Pathを説明(せつめい)し、最適化方法(さいてきかほうほう)は?
CRPはHTML受信(じゅしん)から最初(さいしょ)の画面(がめん)レンダリングまでの経路(けいろ)です。CSSはレンダーブロッキングリソースなのでCritical CSSをインライン化(か)し、JSはdefer/asyncでパーサーブロッキングを防止(ぼうし)します。preloadで重要(じゅうよう)リソースを事前(じぜん)ロードし、不要(ふよう)なCSS/JSを排除(はいじょ)します。
Q3: リフローとリペイントの違(ちが)いは?
リフローはレイアウト再計算(さいけいさん)(位置(いち)、サイズ変更(へんこう))でコストが大(おお)きいです。リペイントは視覚的(しかくてき)なプロパティ(色(いろ)、影(かげ))のみ変更(へんこう)する比較的(ひかくてき)安価(あんか)な作業(さぎょう)です。リフローは常(つね)にリペイントを伴(ともな)いますが、リペイントはリフローなしで発生(はっせい)できます。
Q4: display: noneとvisibility: hiddenの違(ちが)いは?
display: noneはレンダーツリーから完全(かんぜん)に削除(さくじょ)され、スペースを占有(せんゆう)しません。切(き)り替(か)え時(じ)にリフローが発生(はっせい)します。visibility: hiddenはレンダーツリーに残(のこ)りスペースを占有(せんゆう)し、リペイントのみ発生(はっせい)します。
Q5: イベントループでMicrotaskとMacrotaskの実行順序(じっこうじゅんじょ)は?
同期(どうき)コード実行後(じっこうご)、Call Stackが空(から)になると、Microtask Queueのすべてのタスクを先(さき)に処理(しょり)します。Microtask Queueが空(から)になるとレンダリングが必要(ひつよう)なら実行(じっこう)し、Macrotask Queueから1つのタスクを実行(じっこう)します。これを繰(く)り返(かえ)します。
Q6: requestAnimationFrameの役割(やくわり)とsetTimeoutとの違(ちが)いは?
rAFは次(つぎ)のリペイント直前(ちょくぜん)にコールバックを実行(じっこう)し、スムーズな60fpsアニメーションを保証(ほしょう)します。setTimeoutは正確(せいかく)なタイミングを保証(ほしょう)せず、ブラウザのリフレッシュレートと同期(どうき)されないためフレーム落(お)ちが発生(はっせい)する可能性(かのうせい)があります。
Q7: deferとasyncスクリプトの違(ちが)いは?
両方(りょうほう)ともHTMLパースと並行(へいこう)でスクリプトをダウンロードします。deferはDOM完成後(かんせいご)にスクリプト順序(じゅんじょ)で実行(じっこう)し、DOMContentLoaded前(まえ)に実行(じっこう)されます。asyncはダウンロード完了(かんりょう)即時(そくじ)実行(じっこう)で順序(じゅんじょ)を保証(ほしょう)しません。
Q8: GPUアクセラレーションが適用(てきよう)されるCSSプロパティは?
transform、opacity、filterが代表的(だいひょうてき)です。これらのプロパティは合成(ごうせい)(Composite)段階(だんかい)でのみ処理(しょり)され、レイアウトやペイントをスキップします。will-changeでブラウザにヒントを与(あた)えることができます。
Q9: Web Workerの用途(ようと)と制限事項(せいげんじこう)は?
メインスレッドをブロックせずにCPU集約的(しゅうやくてき)な作業(さぎょう)(ソート、暗号化(あんごうか)、画像処理(がぞうしょり))を実行(じっこう)します。制限事項(せいげんじこう)としてDOMに直接(ちょくせつ)アクセスできず、メインスレッドとpostMessageでのみ通信(つうしん)します。
Q10: Virtual DOMが必要(ひつよう)な理由(りゆう)をレンダリングパイプラインの観点(かんてん)から説明(せつめい)してください。
直接(ちょくせつ)DOMを複数回(ふくすうかい)操作(そうさ)すると、各変更(かくへんこう)ごとにリフロー/リペイントが発生(はっせい)します。Virtual DOMはメモリで変更(へんこう)を比較(ひかく)(diff)した後(あと)、最小限(さいしょうげん)の実際(じっさい)のDOM更新(こうしん)を行(おこな)い、リフロー回数(かいすう)を削減(さくげん)します。
クイズ5問(もん)
Q1: 次(つぎ)のコードのコンソール出力順序(しゅつりょくじゅんじょ)は?
console.log('A');
setTimeout(() => console.log('B'), 0);
Promise.resolve().then(() => console.log('C'));
console.log('D');
正解(せいかい): A、D、C、B
同期(どうき)コード(A、D)が最初(さいしょ)に実行(じっこう)され、MicrotaskであるPromise(C)が次(つぎ)、MacrotaskであるsetTimeout(B)が最後(さいご)です。
Q2: CSSでどのプロパティ変更(へんこう)が合成(ごうせい)(Composite)のみ発生(はっせい)するか?
正解(せいかい): transformとopacityです。これらのプロパティはGPU合成(ごうせい)レイヤーで処理(しょり)され、LayoutとPaint段階(だんかい)をスキップします。filterも合成(ごうせい)レイヤーで処理(しょり)されます。
Q3: preloadとprefetchの違(ちが)いは?
正解(せいかい): preloadは現在(げんざい)のページで間(ま)もなく必要(ひつよう)な重要(じゅうよう)リソースを高(たか)い優先度(ゆうせんど)で事前(じぜん)ロードします。prefetchは次(つぎ)のナビゲーションで必要(ひつよう)と予想(よそう)されるリソースを低(ひく)い優先度(ゆうせんど)で事前取得(じぜんしゅとく)します。
Q4: レイアウトトラッシング(Layout Thrashing)とは?
正解(せいかい): DOM読(よ)み取(と)り(offsetWidthなど)と書(か)き込(こ)み(スタイル変更(へんこう))を交互(こうご)に行(おこな)うと、ブラウザが正確(せいかく)な値(あたい)を返(かえ)すために毎回(まいかい)強制(きょうせい)リフローを実行(じっこう)する現象(げんしょう)です。解決法(かいけつほう)は読(よ)みを先(さき)にまとめて行(おこな)い、書(か)きを後(あと)にまとめて行(おこな)うバッチングです。
Q5: display: none、visibility: hidden、opacity: 0の違(ちが)いをレンダリングの観点(かんてん)から説明(せつめい)してください。
正解(せいかい):
display: none: レンダーツリーから完全(かんぜん)に削除(さくじょ)、スペースなし、切(き)り替(か)え時(じ)にリフロー発生(はっせい)visibility: hidden: レンダーツリーに存在(そんざい)、スペース占有(せんゆう)、リペイントのみ発生(はっせい)opacity: 0: レンダーツリーに存在(そんざい)、スペース占有(せんゆう)、合成(ごうせい)レイヤーで処理可能(しょりかのう)、イベントも受信(じゅしん)
参考資料(さんこうしりょう)
- Google - How Browsers Work
- MDN - Critical Rendering Path
- Chrome - Inside Look at Modern Web Browser
- web.dev - Rendering Performance
- MDN - Event Loop
- web.dev - Optimize LCP
- Chrome - Compositing
- MDN - Web Workers API
- web.dev - requestAnimationFrame
- Chrome DevTools - Performance
- web.dev - Core Web Vitals
- Philip Roberts - Event Loop Talk
- CSS Triggers