こんにちは!フロントエンドの現場を渡り歩いているチーフアーキテクトの私です。
Webサイトを作っていて、「なんだかスマホでスクロールするとカクカクするな……」「ボタンを押してから画面が変わるまでに、ほんの一瞬だけ引っかかりを感じるな……」とモヤモヤした経験はありませんか?
「自分のコードの書き方が悪いのかな」って、ちょっと落ち込んだりしていませんか?
大丈夫ですよ、安心してください。そのカクつきや引っかかりの正体は、あなたが未熟なせいではなく、Webブラウザの「お仕事の仕組み(ルール)」を知らなかっただけなんです。
今日は、ブラウザが裏側でどうやって私たちの書いたHTMLやJavaScriptを料理しているのか、そしてその過程で厄介な「渋滞」を起こしてしまうロングタスク(Long Tasks)について、身近な例えを交えながら、とことん優しく解き明かしていきましょう!
—
1. ブラウザは「超優秀だけど、同時に一つのことしかできない職人さん」
まず、Webブラウザの頭の中を覗いてみましょう。
ブラウザは、画面を描画したり、ユーザーのクリックを処理したり、JavaScriptを実行したりと、何でもこなすスーパーマンのように見えますよね。
でも実は、ブラウザの心臓部である「メインスレッド(中心となる作業台)」は、職人気質の一人親方なんです。この親方は、同時に複数の作業ができません。 一度に一つの作業を、順番に片付けていくしかできないのです。
身近な例えで考えてみましょう。
あなたは今、街の人気カフェのカウンターにいます。店員さんは1人だけです。
- お客さんの注文を聞く(ユーザーからのクリックや入力)
- カフェラテを作る(JavaScriptの計算やデータ処理)
- カウンターをピカピカに拭いて、次の人を迎える準備をする(画面のレンダリング・描画)
この店員さん、ものすごく手際が良いのですが、1人しかいないので、もし「めちゃくちゃ手間のかかる特別な一杯(重たい処理)」の注文が入ると、どうなるでしょうか?
その作業に付きっきりになってしまい、他のお客さんの注文を聞けなくなったり、テーブルを拭く手が止まってしまったりしますよね。
これが、Webブラウザのメインスレッドで起きていることの正体です。
—
2. 犯人は「50msの壁」!ロングタスクが画面を凍らせる仕組み
ここで、今日の主役である「ロングタスク(Long Tasks)」が登場します。
Webの世界では、「50ms(ミリ秒=0.05秒)を超える長さの作業」をロングタスクと呼びます。
人間の目は非常にシビアで、画面の更新が1秒間に60回(つまり1コマあたり約16.6ms)行われないと、「カクついている」「滑らかじゃない」と感じてしまいます。
もし、ブラウザの親方が一つの作業(JavaScriptのループ処理や重たい計算など)に没頭し、50ms以上ずっと作業台を占有してしまったらどうなるでしょうか?
1. ユーザーが画面をスクロールしようとする(あるいはボタンを押す)。
2. しかし、親方は「今、大事な作業中だからちょっと待って!」と、その入力を無視せざるを得ない。
3. 画面の描画(アニメーションやスクロールの反映)もストップし、画面がフリーズしたようになる。
これが、ロングタスクがレンダリング(画面を描画するお仕事)を遅延させ、ユーザーにストレスを与えるメカニズムです。
カフェの例えに戻りましょう。
店員さんが、ものすごく時間のかかる「特製サイフォンコーヒー」の抽出を始めたとします。それが終わるまでの5分間、店員さんは他の作業が一切できません。その間、新しく並んだお客さんはずっと待たされることになります。これがロングタスクです。
—
3. どうすればいいの?「タスクを細かく分割する」という魔法
「じゃあ、重たい処理を書くときはどうすればいいの? 全部諦めなきゃいけないの?」
いいえ、そんなことはありません!ここで私たちフロントエンドエンジニアの出番です。
解決策はとってもシンプル。
「一気にやらずに、こまめに休憩を挟む(タスクを分割する)」ことです。
先ほどのカフェの店員さんなら、「サイフォンをセットしたら、一度お湯を沸かしている間に、並んでいるお客さんの注文を1人分だけ聞いておく。またコーヒーに戻る」という風に、細かく仕事を切り替えるイメージです。
これをプログラムの世界では、どのように表現するのでしょうか?
実際のコードを見てみましょう。
改善前:一気にやろうとしてブラウザを窒息させるコード
以下のコードは、数百万回もの重たい計算を、一気にブワッと実行してしまう例です。ブラウザはこれを受け取ると、計算が終わるまで画面の描画を一切放棄してしまいます。
// 【悪い例】一気に重たい処理を実行してしまう(ロングタスクの原因に!)
function runHeavyTask() {
console.log(“重たい処理を開始します…”);
// 途方もない回数のループ(これが50ms以上かかり、ブラウザをフリーズさせる)
for (let i = 0; i < 1000000000; i++) {
// ひたすら計算するだけの重労働
}
console.log("処理が完了しました!");
}
// ボタンを押すと、画面が一時的にカクッと固まる
document.getElementById("myButton").addEventListener("click", runHeavyTask);
改善後:タスクを分割して、ブラウザに「息継ぎ」をさせるコード
では、これをどう直せばいいのでしょうか?
現代のJavaScriptには、ブラウザの親方に「ねえ、ちょっと一息ついて、溜まっている画面描画や他の仕事をしていいよ!」とバトンタッチするための仕組み(`setTimeout` や `requestAnimationFrame`、あるいは `async/await`)が用意されています。
いくつかのパーツ(チャンク)に仕事を分けて、順番に実行するコードを見てみましょう。
/
- 【良い例】重たい処理を小さな小分け(チャンク)にして実行する
/
// 重たい配列を、少しずつ分割して処理する関数
async function runHeavyTaskSafely() {
console.log(“分割した処理を開始します…”);
const totalSteps = 10; // 全体の作業を10個に分割するよ
let currentStep = 0;
function doChunk() {
return new Promise((resolve) => {
// 1回分の小さな仕事(これなら50ms以内に終わる!)
// ※ここでは擬似的に重たい処理をシミュレーションしています
const start = performance.now();
while (performance.now() – start < 10) {
// 10msだけ計算する
}
currentStep++;
resolve();
});
}
// ループを回しつつ、1回終わるごとにブラウザに主導権を「お返し」する
while (currentStep < totalSteps) {
await doChunk(); // ここで「ちょっと待ってね、描画や他の仕事をしていいよ」とブラウザに譲る
console.log(`進捗: ${currentStep} / ${totalSteps} 完了`);
}
console.log("すべての処理がスムーズに完了しました!");
}
// これならボタンを押しても画面がカクつかず、なめらかに動きます
document.getElementById("myButton").addEventListener("click", runHeavyTaskSafely);
このコードのポイントは、`await doChunk();` の部分です。
一気にすべての仕事を終わらせるのではなく、「10msだけ計算したら、一度ブラウザに『お疲れ様!画面の描画やユーザーのスクロール処理をしていいよ!』とバトンを渡す」というキャッチボールを挟んでいます。
これによって、ブラウザの親方は定期的に息継ぎができるため、ユーザーが画面をスクロールしたり、他のボタンを押したりしたときにも、即座に反応できるようになるのです。
---
まとめ:ブラウザに優しいコードが、ユーザーに優しいWebを作る
今回は、ロングタスクがレンダリングを遅延させる仕組みと、タスク分割による改善の魔法についてお話ししました。
- ブラウザのメインスレッドは一人親方。同時に一つの仕事しかできない。
- 50msを超える「ロングタスク」は、親方を拘束して画面をカクつかせる原因になる。
- 重たい処理は小さく分割(チャンク化)し、ブラウザに「息継ぎのタイミング(お仕事のバトンタッチ)」をプレゼントしてあげよう。
Web制作やプログラミングを学び始めたばかりの頃は、「動けばいいや」と書きがちですが、こうしたブラウザの「中の人の気持ち」を少しだけ想像してあげると、書くコードの質が劇的に変わってきます。そして何より、あなたの作ったWebサイトを使うユーザーが、ストレスなく快適に笑顔で過ごせるようになりますよ。
焦らず、一歩ずつ、ご自身のペースで進んでいきましょう。
あなたのフロントエンドライフを、これからも応援しています!

コメント