JavaScript | Web API:タイマー・スケジューリング - UI 更新との関係

JavaScript JavaScript
スポンサーリンク

UI 更新とタイマーの関係は「JavaScript とブラウザの役割分担」を理解すると一気に分かりやすくなる

タイマー(setTimeout / setInterval / requestAnimationFrame)を使うとき、
初心者がよくつまずくのが 「UI が更新されない」「画面が固まる」 といった現象です。

これは、
JavaScript が UI を直接描画しているわけではない
という前提を理解するとスッキリします。

JavaScript は「UI をこう変えて」とブラウザに指示するだけで、
実際の描画はブラウザが「描画タイミング」に合わせて行います。

この「描画タイミング」と「タイマーの動き」がズレると、
UI が更新されないように見えたり、カクついたりするわけです。


JavaScript とブラウザの「描画サイクル」を理解する

JavaScript は UI を“即座に”変えているわけではない

例えば、こう書いたとします。

box.style.left = "100px";
JavaScript

これは「左に 100px 動かして」とブラウザに依頼しているだけで、
その瞬間に画面が描き変わるわけではありません。

ブラウザは、

JavaScript の処理が終わる
→ レイアウト計算
→ ペイント
→ 実際の画面に反映

という流れで描画します。

つまり、
JavaScript の処理が長引くと、UI の更新も遅れる
ということです。

setTimeout / setInterval は「描画タイミングを知らない」

setTimeoutsetInterval は、

「◯ミリ秒後に実行してね」

というだけで、
ブラウザの描画タイミングとは無関係 です。

そのため、

100ms ごとに UI を更新したい
→ でも描画は 16ms ごと(60fps)
→ タイミングがズレてカクつく

という現象が起きます。

requestAnimationFrame は「描画直前に呼ばれる」

一方、requestAnimationFrame は、

「次の描画直前に呼んでね」

という API です。

だから、

UI を滑らかに動かしたい
→ requestAnimationFrame が最適

という構造になっています。


UI が更新されない典型例とその理由

例:重い処理が UI 更新をブロックする

box.textContent = "更新中…";

setTimeout(() => {
  const start = Date.now();
  while (Date.now() - start < 2000) {
    // 2秒間ブロックする重い処理
  }
  box.textContent = "完了!";
}, 0);
JavaScript

一見、

「すぐ“更新中…”が表示されて、2 秒後に“完了!”になる」

と思いがちですが、実際には、

“更新中…”が表示されないまま 2 秒固まる → “完了!”が表示される

という動きになります。

理由は、

setTimeout(..., 0) が「すぐ実行」ではなく「後回し」
その後の while が 2 秒間メインスレッドを占有
→ ブラウザが描画できない
→ “更新中…” が反映されない

という流れです。

つまり、
UI 更新は JavaScript が止まっている間は絶対に起きない
ということです。


UI 更新とタイマーを正しく連携させる方法

UI を更新したい → requestAnimationFrame を使う

アニメーションや連続した UI 更新は、
requestAnimationFrame が最適です。

let x = 0;

function move() {
  x += 2;
  box.style.left = `${x}px`;

  if (x < 300) {
    requestAnimationFrame(move);
  }
}

requestAnimationFrame(move);
JavaScript

これなら、

描画タイミングに合わせて更新
→ カクつきが少ない
→ CPU の無駄も少ない

という理想的な動きになります。

重い処理は UI 更新と同じタイミングでやらない

重い処理を UI 更新と同じタイミングでやると、
UI が固まります。

そこで使えるのが、

setTimeout(分割実行)
requestIdleCallback(ブラウザがヒマなときに実行)
Web Worker(別スレッドで実行)

などです。

例えば、重い処理を小分けにするなら:

function heavyWorkChunk() {
  for (let i = 0; i < 1000; i++) {
    // 小さな処理
  }

  if (まだ残りがある) {
    setTimeout(heavyWorkChunk, 0); // UI をブロックしない
  }
}

heavyWorkChunk();
JavaScript

こうすると、
UI 更新のタイミングを奪わずに重い処理を進められます。


タイマーと UI 更新の「ズレ」を理解する

setInterval は UI 更新とズレやすい

setInterval(() => {
  box.style.left = `${x}px`;
  x += 2;
}, 16);
JavaScript

16ms は 60fps の理論値ですが、
実際には、

処理が重い
ブラウザの描画タイミングとズレる
バックグラウンドで間引かれる

などの理由で、
カクつきやズレが発生します。

だから、アニメーションには向きません。

setTimeout も「UI 更新の直前に実行される」とは限らない

setTimeout(() => {
  box.style.left = "100px";
}, 1000);
JavaScript

これは「1 秒後に UI を更新する」だけで、
描画タイミングに合わせてくれるわけではありません。

そのため、
連続した UI 更新には不向きです。


UI 更新を滑らかにするための実践的な考え方

UI を変える処理は「短く」「軽く」

UI 更新の直前に重い処理があると、
描画が遅れます。

UI を更新する関数は、

DOM 操作は最小限
計算は軽く
重い処理は別のタイミングに回す

という設計が重要です。

UI 更新のタイミングは「ブラウザが決める」

JavaScript が「今すぐ更新して」と言っても、
実際に描画されるのはブラウザのタイミングです。

だから、

UI を滑らかにしたい
→ requestAnimationFrame
UI を固めたくない
→ 重い処理は分割 or idle 時に実行

という役割分担が必要です。


初心者として「UI 更新とタイマーの関係」で本当に掴んでほしいこと

UI は JavaScript が直接描くのではなく、ブラウザが描画タイミングで反映する
setTimeout / setInterval は描画タイミングを知らないので、UI 更新がズレたりカクついたりする
重い処理があると UI 更新がブロックされる(JavaScript はシングルスレッド)
アニメーションや連続 UI 更新は requestAnimationFrame が最適
重い処理は分割するか、idle 時に回すか、Web Worker に逃がす

まずは、

setInterval でアニメーション
→ requestAnimationFrame に書き換える

という小さな実験をしてみると、
UI 更新とタイマーの関係が一気に腑に落ちるはずです。

タイトルとURLをコピーしました