1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

INP(Interaction to Next Paint)完全攻略:シングルスレッドJavaScriptの特性と最適化手法

Posted at

はじめに

2024年3月12日、Googleは新しいCore Web Vitalsの指標として INP(Interaction to Next Paint) を正式に導入し、従来の FID(First Input Delay) に代わる重要な指標となりました。

INPは、ユーザーがページ上で行うすべてのインタラクションの応答性を測定し、より実際のユーザー体験に近い評価を可能にします。本記事では、INPの仕組みから具体的な最適化手法まで、実践的な観点から解説します。

INPとは何か

INPの基本概念

INP(Interaction to Next Paint) は、ユーザーのインタラクション(クリック、タップ、キーボード入力)から、ブラウザが次のフレームを描画するまでの時間を測定する指標です。

FIDとINPの比較

項目 FID(旧指標) INP(新指標)
測定対象 最初のインタラクションのみ すべてのインタラクション
測定範囲 入力遅延のみ 入力遅延 + 処理時間 + 描画遅延
評価方法 単一の値 最悪値(98パーセンタイル)
実用性 限定的 より実際のUXに近い

INPの構成要素

INPは以下の3つの要素で構成されています:

要素 説明 主な影響要因
入力遅延 インタラクションからイベントハンドラー実行開始まで メインスレッドのブロック
処理時間 イベントハンドラーの実行時間 JavaScript処理の重さ
描画遅延 処理完了から次のフレーム描画まで DOMサイズ、CSS複雑さ

INPの評価基準

評価 時間 状態
🟢 良好 ≤200ms 優秀な応答性
🟡 改善必要 200-500ms 改善の余地あり
🔴 不良 >500ms 緊急対応が必要

INPが悪化する原因:シングルスレッドの特性

JavaScriptのシングルスレッド問題

JavaScriptは シングルスレッド 言語として設計されており、これがINP悪化の根本原因です。

メインスレッドの役割

主な問題とその影響

問題 説明 INPへの影響
ロングタスク 50ms以上の処理 🔴 高(メインスレッドブロック)
大きなDOM 1400ノード超 🟡 中(レンダリング重い)
重いJavaScript 同期的な大量処理 🔴 高(処理時間増加)
複雑なCSS 複雑なセレクター 🟡 中(描画遅延)

問題の診断方法

// 簡単なINP測定コード
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.entryType === 'event' && entry.duration > 200) {
      console.warn('遅いインタラクション検出:', {
        要素: entry.target,
        時間: entry.duration,
        種類: entry.name
      });
    }
  }
});
observer.observe({ entryTypes: ['event'] });

解決手法1:デバウンス処理

デバウンスとは

連続するイベントを制御し、最後のイベントから一定時間経過後に処理を実行する手法です。

効果的な使用場面

場面 効果 推奨遅延時間
検索入力 API呼び出し削減 300-500ms
スクロール 処理頻度制限 16ms(60FPS)
リサイズ レイアウト計算削減 250ms
フォーム入力 バリデーション最適化 500ms

実装例(簡潔版)

// デバウンス関数
function debounce(func, delay) {
  let timeoutId;
  return (...args) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => func(...args), delay);
  };
}

// 使用例:検索入力
const searchInput = document.getElementById('search');
const debouncedSearch = debounce(performSearch, 300);
searchInput.addEventListener('input', (e) => {
  debouncedSearch(e.target.value);
});

解決手法2:メインスレッド解放

タスク分割の基本戦略

手法 適用場面 実装難易度
setTimeout(0) 簡単な分割 🟢 易
requestIdleCallback 空き時間活用 🟡 中
MessageChannel 高精度制御 🔴 難

実装パターン

// パターン1: 簡単なタスク分割
async function processLargeData(data) {
  const chunkSize = 100;
  for (let i = 0; i < data.length; i += chunkSize) {
    const chunk = data.slice(i, i + chunkSize);
    processChunk(chunk);
    
    // メインスレッドに制御を戻す
    await new Promise(resolve => setTimeout(resolve, 0));
  }
}

// パターン2: 空き時間活用
function processInIdle(tasks) {
  function processTasks() {
    while (tasks.length > 0) {
      const task = tasks.shift();
      task();
      
      // 時間切れチェック
      if (performance.now() % 5 === 0) break;
    }
    
    if (tasks.length > 0) {
      requestIdleCallback(processTasks);
    }
  }
  requestIdleCallback(processTasks);
}

解決手法3:Web Worker活用

Web Workerの適用場面

処理タイプ 適用度 理由
数値計算 🟢 最適 CPU集約的、DOM不要
データ変換 🟢 最適 大量データ処理
画像処理 🟢 最適 重い計算処理
DOM操作 🔴 不適 Worker内でDOM不可
API呼び出し 🟡 場合による ネットワーク処理

基本的な実装パターン

// メインスレッド側
class SimpleWorker {
  constructor(workerScript) {
    this.worker = new Worker(workerScript);
  }
  
  async execute(data) {
    return new Promise((resolve) => {
      this.worker.onmessage = (e) => resolve(e.data);
      this.worker.postMessage(data);
    });
  }
}

// 使用例
const worker = new SimpleWorker('calculator.js');
const result = await worker.execute({ numbers: [1,2,3,4,5] });
// Worker側(calculator.js)
self.onmessage = function(e) {
  const { numbers } = e.data;
  
  // 重い計算処理
  const result = numbers.map(n => fibonacci(n));
  
  self.postMessage(result);
};

function fibonacci(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

最適化の優先順位

段階的アプローチ

段階 対策 効果 実装コスト
1. 基本 デバウンス適用 🟡 中 🟢 低
2. 中級 タスク分割 🟢 高 🟡 中
3. 上級 Web Worker 🟢 高 🔴 高

効果測定の指標

// 簡単なINP監視
class INPMonitor {
  constructor() {
    this.interactions = [];
    this.setupObserver();
  }
  
  setupObserver() {
    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        if (entry.entryType === 'event') {
          this.interactions.push({
            duration: entry.duration,
            type: entry.name,
            timestamp: Date.now()
          });
        }
      }
    });
    observer.observe({ entryTypes: ['event'] });
  }
  
  getINP() {
    if (this.interactions.length === 0) return 0;
    const durations = this.interactions.map(i => i.duration).sort((a, b) => a - b);
    
    // 98パーセンタイルまたは最大値
    return this.interactions.length < 50 
      ? Math.max(...durations)
      : durations[Math.floor(durations.length * 0.98)];
  }
  
  getReport() {
    const inp = this.getINP();
    const slowCount = this.interactions.filter(i => i.duration > 200).length;
    
    return {
      inp: Math.round(inp),
      totalInteractions: this.interactions.length,
      slowInteractions: slowCount,
      status: inp <= 200 ? '良好' : inp <= 500 ? '改善必要' : '不良'
    };
  }
}

// 使用例
const monitor = new INPMonitor();
setInterval(() => {
  console.log('INPレポート:', monitor.getReport());
}, 30000);

実践的なチェックリスト

即座に実行できる対策

  • 検索・入力フィールドにデバウンス適用(300ms推奨)
  • スクロールイベントにスロットリング適用(16ms推奨)
  • 不要なイベントリスナーの削除
  • DOMサイズの確認(1400ノード以下に)
  • 長時間実行される処理の特定

中級者向け対策

  • 重い処理のタスク分割実装
  • requestIdleCallbackの活用
  • 画像・動画の遅延読み込み
  • CSSアニメーションの最適化

上級者向け対策

  • Web Workerによる計算処理の分離
  • SharedArrayBufferの活用(対応ブラウザのみ)
  • Service Workerとの連携
  • リアルタイム監視システムの構築

まとめ

INPの最適化は、段階的なアプローチ が重要です:

重要なポイント

  1. シングルスレッドの理解 - JavaScriptの特性を理解し、メインスレッドをブロックしない設計
  2. 段階的な最適化 - デバウンス → タスク分割 → Web Worker の順で実装
  3. 継続的な測定 - 最適化効果を定量的に測定し、継続的改善
  4. ユーザー中心の視点 - 技術指標だけでなく、実際のUX向上を最優先

今後の展望

Web技術の発展に伴い、INPの最適化手法も進化し続けています。新しいAPIや技術動向を常にキャッチアップし、ユーザーにとって最高の体験を提供することが重要です。

INPの最適化は一度限りの作業ではなく、継続的な改善プロセス です。定期的な測定と分析を通じて、常にユーザー体験の向上を目指しましょう。

参考文献

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?