ユーザー体験を殺す「見えない敵」をあぶり出す:Long Tasks APIの実践的アプローチ
「なぜかボタンが反応しない」「スクロールがカクつく」。そんな時、多くのエンジニアは「メモリリークか?」「重いライブラリのせいか?」と闇雲にプロファイラを回しがちだ。だが、ブラウザのメインスレッドが何によって「人質」に取られているのか、その正体を正確に把握しているだろうか。
今日は、フロントエンドのパフォーマンスチューニングにおいて、避けては通れない「メインスレッドの占有」という怪物と戦うための武器、Long Tasks APIについて語ろう。
1. なぜ「50ms」が境界線なのか
ブラウザのメインスレッドは、いわば「一点集中型の職人」だ。JavaScriptの実行、リフロー、リペイント、そしてユーザーの入力への応答。これら全てをたった一つのスレッドでこなしている。
ブラウザのレンダリングエンジンは、一般的に60fps(約16.6msごとの描画)を目指して動いている。しかし、JavaScriptのタスクが50msを超えて居座り続けると、ユーザーの入力(タップやスクロール)は後回しにされる。これが「入力遅延(Input Delay)」の正体だ。
Googleが推奨するCore Web VitalsのINP(Interaction to Next Paint)を改善したいのであれば、この「50ms以上居座る迷惑なタスク」を排除しなければならない。
2. 現場で「泥臭く」ログを吐き出させる方法
`PerformanceObserver`を使うのが定石だ。Chrome DevToolsのパフォーマンスパネルを見るのも良いが、あれはあくまで「手元の環境」の話。ユーザーが実際の端末で、どのような重い処理を踏んでいるのかを知るには、ブラウザのAPIから直接報告させるしかない。
以下のコードを、アプリの初期化タイミング(できればバンドルの一番最初)に仕込んでみてほしい。
/
- Long Tasks を監視するObserverのセットアップ
- 50ms以上のメインスレッド占有を検出し、その詳細をログに出力する
/
function observeLongTasks() {
if (!(‘PerformanceObserver’ in window)) {
console.warn(‘このブラウザはLong Tasks APIをサポートしていません。’);
return;
}
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// entryType は ‘longtask’
console.warn(‘⚠️ ロングタスクを検出:’, {
duration: `${entry.duration.toFixed(2)}ms`, // 処理にかかった時間
startTime: entry.startTime, // ページのロード開始からの経過時間
// attribution はどの処理が原因かを探るヒントになる(containerIdなど)
attribution: entry.attribution
});
// 実際の実務では、ここでSentryやDatadog等の監視ツールへ送るのがベスト
// sendToAnalytics({ duration: entry.duration, … });
}
});
// ‘longtask’ エントリータイプを監視対象に登録
observer.observe({ entryTypes: [‘longtask’] });
}
// アプリ起動時に実行
observeLongTasks();
3. 注意点:このAPIは「万能薬」ではない
このコードを実装すると、驚くほど多くの警告が出るはずだ。特にReactの巨大なレンダリングや、サードパーティ製タグの非同期読み込みがメインスレッドを食いつぶしているのが手に取るようにわかるだろう。
ただし、ここで一つ注意点がある。`attribution`プロパティは、セキュリティ上の制約で「詳細なJSのスタックトレース」までは教えてくれない。
「どこで」止まったかを知るには、さらに一歩踏み込む必要がある。
- サードパーティのスクリプトなら: `attribution`内の`name`フィールドで、どのスクリプトが関与しているか特定できる。
- 自社コードなら: `Scheduler.postTask` APIや、`requestIdleCallback`を使って、タスクを小さな断片に分割(タスクの細分化)する設計に切り替える必要がある。
4. シニアエンジニアからの助言
ロングタスクを検知した後の戦い方はシンプルだ。
1. 「非同期化」の徹底: 重い計算処理はWorkerへ逃がす。DOMに直接触れない処理はすべてメインスレッドから追い出す。
2. 「分割」の哲学: 500msの処理を一つやるのではなく、50msの処理を10回やる。こうすることで、各処理の合間にブラウザがユーザー入力を受け付ける隙間ができる。
3. 「不要なものは捨てる」: 結局のところ、ロングタスクの多くは「なくてもいい処理」が原因だ。ライブラリのロード順や、不要なデータ変換を見直すだけで、警告は激減する。
ブラウザのレンダリングエンジンは、私たちが書いたコードを律儀に、しかし時に冷酷に実行する。その「実行の履歴」を覗き見るのがこのLong Tasks APIだ。
「なぜ重いのか?」と悩む段階はもう卒業しよう。データをもとに、どこでメインスレッドが悲鳴を上げているのかを特定し、ユーザーの指先に吸い付くような滑らかな体験を取り戻してほしい。健闘を祈る。

コメント