パフォーマンス指標として INP(Interaction to Next Paint) や TBT(Total Blocking Time) が存在
これらは「どれだけメインスレッドがブロックされたか」を数値化
メインスレッド時間の使い方:4つの基本戦略
分割(splitting) :長い処理を小分けにし、間で制御を返す
バッチ化(batching) :頻繁な処理をまとめて実行
優先順位付け(prioritizing) :複数タスクの実行順序を最適化
遅延実行(deferring) :今すぐ不要な処理を後回し
まずは 分割 が基礎。分割があってこそバッチ化や優先順位付け、遅延が可能
分割の実践例:ライブチャットの描画
ライブ配信のチャットでは 大量メッセージが一度に到着 しがち
一気にDOM描画すると、 入力やアニメーションが全て停止
20件ごとなどに分割し、setTimeoutで制御を返す ことで、間に描画や入力処理が挟まる
分割によって「仕事の合間」にブラウザが他の処理を進められるように
setTimeout は次のタスクに処理を回すクラシックな方法
// チャットメッセージを分割描画
async function renderChats(chats) {
let count = 0;
for (const chat of chats) {
appendChatNode(chat);
if (++count % 20 === 0) {
await new Promise((resolve) => setTimeout(resolve, 0));
}
}
}
分割しても総作業量は変わらず、全体の処理時間はむしろ増加
しかし ユーザー体感のスムーズさが大幅改善。描画や入力が割り込めるため
時間基準での分割:アニメーションとの協調
アニメーションやスクロール中は 件数ではなく経過時間で分割 する方が安全
requestAnimationFrame を使い、フレームごとに少しずつ処理を進める
こうすることで アニメーションと重い処理が同時進行 し、どちらも滑らかに
async function processDuringAnimation(items) {
let i = 0;
let frameStart = performance.now();
while (i < items.length) {
while (i < items.length && performance.now() - frameStart < 5) {
doWork(items[i++]);
}
frameStart = await new Promise(requestAnimationFrame);
}
}