こんにちは!フロントエンドの現場を長年歩んできたチーフアーキテクトの私です。
Webサイトを作っていて、「なんだかこのボタン、クリックしても反応するまでにワンテンポ遅れるな…」「スマホでスクロールすると、カクカクして気持ち悪いな…」と感じたことはありませんか?
コードはちゃんと書いたはずなのに、なぜか滑らかに動かない。このもどかしさ、実務を経験している人なら誰もが一度は頭を抱えるポイントです。大丈夫ですよ、あなたが下手なわけではありません。犯人は、Webブラウザの心臓部である「メインスレッド」の渋滞なんです。
今回は、ブラウザの裏側で何が起きているのか、そしてどうやってこの渋滞をスッキリ解消すればいいのかを、身近な例えを交えながら一緒に紐解いていきましょう!
—
メインスレッドは「超多忙なワンオペ店員」
まず、Webブラウザが画面を表示して動かす仕組みを、街の人気カフェに例えてみましょう。
このカフェのカウンターには、「メインスレッド」という名前の店員がたった一人だけ立っています。この店員さんは超がつくほどの働き者ですが、一度に一つの仕事しかできません(シングルスレッド)。
このワンオペ店員さんは、毎日こんな仕事を一人でこなしています:
1. HTMLやCSSを読んで、お店のレイアウトを考える(DOM/CSSOMの構築)
2. JavaScriptのプログラムを読んで、複雑な計算やデータ処理をする
3. ユーザーがボタンをクリックしたときの処理をする
4. 画面をきれいに塗り替えて、動かす(レンダリング)
想像してみてください。お客さんが次々とやってきて、「この計算をして!」「ボタンを押されたからアニメーションを動かして!」と、重たい注文を次々に投げつけてきます。店員さんは休む暇もなく働き続けますが、あまりに重い仕事を任されると、手が回らなくなってしまいますよね。
これが、Webの世界でいう「メインスレッドのブロッキング」です。
—
悪魔の数字「50ms(ミリ秒)」の正体
Webの世界には、「ロングタスク(Long Task)」という専門用語があります。これは、「50ミリ秒(0.05秒)を超える処理」のことです。
「たったの0.05秒でしょ?人間には違いがわからないよ」と思いますよね?
ところが、人間の脳とWebブラウザの感覚は、私たちが思っているよりもずっとシビアです。
Webブラウザは、なめらかなアニメーションを維持するために、1秒間に60回(つまり約16.6ミリ秒に1回)のペースで画面を新しく描き替える必要があります(これが「60fps」の正体です)。
もし、ワンオペ店員さんが「50ms以上かかる重たい計算」を始めてしまうとどうなるでしょう?
店員さんはその計算に釘付けになり、お客さん(ユーザー)が「画面をスクロールしたい」「ボタンをタップしたい」と言っても、完全に無視(フリーズ)せざるを得なくなります。
結果として、画面がカクついたり、ボタンを押してから反応するまでにイライラするような「タイムラグ」が生まれてしまうのです。これが、ユーザーが「このサイト、なんか重いな…」と感じる瞬間です。
—
渋滞を解消する魔法:「タスクの分割」
では、このワンオペ店員さんが過労死してしまわないように、どうすればいいのでしょうか?
答えはシンプルです。「大きな仕事は、小さく小分けにして、間に休憩を挟む」ことです。
例えば、1000個の重たい荷物を一人で一気に運ぼうとすると、途中で息が切れて動けなくなってしまいますよね。でも、「100個ずつ運ぶ ➔ 一度お客さんの注文を聞く ➔ また100個運ぶ」という風に、間にこまめな「隙間時間」を作ってあげれば、店員さんは他の人の相手もできるようになります。
これが、プログラミングの世界でいう「タスクの分割(Task Chunks)」です。
実際のコードで見てみましょう
初心者の方でもイメージしやすいように、簡単な JavaScript の例を用意しました。
例えば、「ものすごい数のデータ(10万件)を処理する」という重たい処理を考えてみます。
【悪い例】一気に全部やろうとしてメインスレッドを窒息させるコード
// 10万件の重たいデータを一気に処理する関数
function heavyProcessAllAtOnce(items) {
// このループが長すぎて、ブラウザが固まる(ロングタスク発生!)
for (let i = 0; i < items.length; i++) {
// 重たい計算や処理のシミュレーション
doSomethingComplex(items[i]);
}
console.log("すべての処理が完了しました!");
}
この書き方だと、ループが終わるまで店員さんは他の仕事を一切できなくなり、画面がフリーズします。
【良い例】仕事を小分けにしてメインスレッドに「息継ぎ」をさせるコード
そこで、JavaScriptの `setTimeout` や `requestAnimationFrame`、あるいはモダンな非同期処理(`async/await`)の仕組みを使って、処理を細切れにしてみましょう。
// データを少しずつ処理して、メインスレッドを解放する優しいコード
async function heavyProcessSafely(items) {
const chunkSize = 500; // 一度に処理する数を手頃なサイズに決める
for (let i = 0; i < items.length; i += chunkSize) {
// 500件分のブロック(塊)を作る
const chunk = items.slice(i, i + chunkSize);
// このブロックの中身を処理
chunk.forEach(item => {
doSomethingComplex(item);
});
// ★ここが超重要!「ちょっと一息ついていいよ」とブラウザに主導権を返す
// これにより、ブラウザは間に画面の描画やユーザーのクリック処理を挟むことができます。
await new Promise(resolve => setTimeout(resolve, 0));
}
console.log(“息継ぎをしながら、すべての処理が完了しました!”);
}
このコードでは、500件処理するごとに `setTimeout` を挟むことで、ワンオペ店員さんに「今、他にお客さん困ってない?大丈夫?」と確認する隙(ブレイクタイム)を与えています。これだけで、画面のカクつきやフリーズ劇的に改善されるんです。
—
現場のチーフアーキテクトからのアドバイス
Web制作やフロントエンドの学習をしていると、ついつい「いかに動くコードを素早く書くか」に意識が向きがちです。私も昔はそうでした。動けば正義、だと思っていました。
でも、本当に優れたWebサイトやアプリケーションは、コードの正しさだけではなく、「使っているユーザーの心地よさ(レスポンスの速さ)」への優しさが細部に宿っています。
もしあなたが今後、「ボタンの反応が悪いな」「アニメーションがカクつくぞ」という壁にぶつかったら、ぜひ今回の「メインスレッドのワンオペ店員」と「50msの壁」を思い出してください。
「あ、今この店員さん、働きすぎでパンクしてるな。仕事を小分けにしてあげよう」と優しくコードを書き直してあげれば、きっとあなたの作るWebサイトは、見違えるほど滑らかで快適に動き始めますよ。
焦らず、一歩ずつ、美味しいコードを一緒に作っていきましょうね!

コメント