Performance API 実践入門:Long Tasks、INP、パフォーマンス監視システム

性能优化(更新: 2026年6月20日)

Performance API エコシステム概要

ブラウザのPerformance APIは、ナビゲーションタイミングから要素レンダリング、長時間タスク検出、インタラクション応答性まで、パフォーマンス監視の全チェーンをカバーする完全な測定・監視インターフェースを提供します。

API 監視対象 タイプ
PerformanceObserver 全パフォーマンスエントリのオブザーバー コア
PerformanceNavigationTiming ページナビゲーションと読み込み ナビゲーション
PerformanceResourceTiming リソース読み込み時間 リソース
PerformanceLongTaskTiming 長時間タスク(>50ms) インタラクション
PerformanceEventTiming ユーザーインタラクション遅延 インタラクション
PerformanceElementTiming 要素レンダリング時間 レンダリング
PerformanceLayoutShift レイアウトシフト 安定性
PerformancePaintTiming 初回ペイント/初回コンテンツペイント レンダリング

PerformanceObserver の基礎

コアパターン

const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log(entry.name, entry.startTime, entry.duration);
  }
});

observer.observe({ type: 'longtask', buffered: true });

複数タイプの監視

const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    switch (entry.entryType) {
      case 'longtask':
        reportLongTask(entry);
        break;
      case 'event':
        reportEventTiming(entry);
        break;
      case 'layout-shift':
        reportLayoutShift(entry);
        break;
      case 'element':
        reportElementTiming(entry);
        break;
    }
  }
});

observer.observe({
  type: ['longtask', 'event', 'layout-shift', 'element'],
  buffered: true
});

Long Tasks 監視

Long Tasksはメインスレッドで50ms以上実行されるタスクで、ユーザーインタラクションをブロックし、ページのカクつきを引き起こします。

基本監視

const longTaskObserver = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log({
      name: entry.name,
      startTime: entry.startTime.toFixed(2),
      duration: entry.duration.toFixed(2),
      culprit: entry.attribution?.[0]?.containerType,
      containerName: entry.attribution?.[0]?.containerName,
    });
  }
});

longTaskObserver.observe({ type: 'longtask', buffered: true });

長時間タスクの帰属分析

function analyzeLongTask(entry) {
  const attribution = entry.attribution?.[0];
  if (!attribution) return { type: 'unknown' };

  return {
    type: attribution.containerType,
    containerName: attribution.containerName,
    containerId: attribution.containerId,
    containerSrc: attribution.containerSrc,
    duration: entry.duration,
    startTime: entry.startTime,
  };
}

const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    const analysis = analyzeLongTask(entry);
    reportToAnalytics('longtask', analysis);
  }
});

observer.observe({ type: 'longtask', buffered: true });

長時間タスク最適化戦略

戦略 説明
タスク分割 大きなタスクを<50msのチャンクに分割 scheduler.yield()
遅延実行 非クリティカルタスクをアイドル期間に延期 requestIdleCallback
Web Worker CPU集約的処理をメインスレッドから分離 データ処理、ソート
コード分割 オンデマンド読み込みで初期JS量を削減 動的import()
スケジューリング優先度 Scheduler APIで実行順序を制御 scheduler.postTask
async function yieldToMain() {
  if ('scheduler' in window && 'yield' in scheduler) {
    return scheduler.yield();
  }
  return new Promise((resolve) => setTimeout(resolve, 0));
}

async function processLargeList(items) {
  const results = [];
  for (let i = 0; i < items.length; i++) {
    results.push(processItem(items[i]));
    if (i % 100 === 0) await yieldToMain();
  }
  return results;
}

Interaction to Next Paint (INP)

INPはCore Web Vitalsのインタラクション応答指標で、ユーザーインタラクションから次回ペイントまでの遅延を測定します。

INP評価基準

評価 INP値 ユーザー体験
良好 ≤ 200ms 高速応答
改善必要 200-500ms 遅延を感知
悪い > 500ms 明らかなカクつき

INPの監視

let maxInteraction = 0;
let worstInteraction = null;

const inpObserver = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (!entry.interactionId) continue;

    const duration = entry.duration;
    if (duration > maxInteraction) {
      maxInteraction = duration;
      worstInteraction = {
        interactionId: entry.interactionId,
        type: entry.name,
        duration,
        startTime: entry.startTime,
        processingStart: entry.processingStart,
        processingEnd: entry.processingEnd,
        inputDelay: entry.processingStart - entry.startTime,
        processingDuration: entry.processingEnd - entry.processingStart,
        presentationDelay: entry.duration - (entry.processingEnd - entry.startTime),
      };
    }
  }
});

inpObserver.observe({ type: 'event', buffered: true, durationThreshold: 16 });

document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') {
    if (worstInteraction) {
      reportToAnalytics('inp', {
        value: maxInteraction,
        rating: maxInteraction <= 200 ? 'good' : maxInteraction <= 500 ? 'needs-improvement' : 'poor',
        ...worstInteraction,
      });
    }
  }
});

INP遅延の分解

ユーザーインタラクション → 入力遅延 → イベント処理 → 表示遅延 → 次フレームペイント
                       ──────────────────────────────────────────────────
                       |←                INP (duration)                →|

入力遅延:startTime → processingStart
  原因:メインスレッドが長時間タスクでブロック

イベント処理:processingStart → processingEnd
  原因:イベントコールバックの実行が遅い

表示遅延:processingEnd → 次フレームペイント
  原因:重いrAFコールバックやスタイル/レイアウト計算

Element Timing 監視

Element Timingは重要コンテンツのレンダリング時間を追跡し、LCP要素やカスタム重要要素に適用できます。

<img src="hero.jpg" elementtiming="hero-image" />
<p elementtiming="hero-text">重要コンテンツ</p>
<div elementtiming="main-content">...</div>
const elementObserver = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log({
      identifier: entry.identifier,
      startTime: entry.startTime,
      renderTime: entry.renderTime,
      loadTime: entry.loadTime,
      size: entry.size,
      url: entry.url,
      elementType: entry.element?.tagName,
    });
  }
});

elementObserver.observe({ type: 'element', buffered: true });

RUM監視システムの構築

コアメトリクスコレクター

class PerformanceMonitor {
  constructor(options = {}) {
    this.endpoint = options.endpoint;
    this.metrics = {};
    this.observers = [];
  }

  start() {
    this.collectWebVitals();
    this.collectLongTasks();
    this.collectResourceTiming();
    this.collectNavigationTiming();
    this.scheduleReport();
  }

  collectWebVitals() {
    this.observe('event', (entry) => {
      if (!entry.interactionId) return;
      this.metrics.inp = Math.max(this.metrics.inp ?? 0, entry.duration);
    });

    this.observe('largest-contentful-paint', (entry) => {
      this.metrics.lcp = entry.startTime;
    });

    this.observe('layout-shift', (entry) => {
      if (!entry.hadRecentInput) {
        this.metrics.cls = (this.metrics.cls ?? 0) + entry.value;
      }
    });

    this.observe('first-input', (entry) => {
      this.metrics.fid = entry.processingStart - entry.startTime;
    });
  }

  collectLongTasks() {
    this.observe('longtask', (entry) => {
      if (!this.metrics.longTasks) this.metrics.longTasks = [];
      this.metrics.longTasks.push({
        duration: entry.duration,
        startTime: entry.startTime,
        culprit: entry.attribution?.[0]?.containerType,
      });
    });
  }

  collectResourceTiming() {
    this.observe('resource', (entry) => {
      if (entry.duration > 1000) {
        if (!this.metrics.slowResources) this.metrics.slowResources = [];
        this.metrics.slowResources.push({
          name: entry.name,
          duration: entry.duration,
          initiatorType: entry.initiatorType,
          transferSize: entry.transferSize,
        });
      }
    });
  }

  collectNavigationTiming() {
    const [nav] = performance.getEntriesByType('navigation');
    if (nav) {
      this.metrics.ttfb = nav.responseStart - nav.requestStart;
      this.metrics.domContentLoaded = nav.domContentLoadedEventEnd - nav.startTime;
      this.metrics.loadComplete = nav.loadEventEnd - nav.startTime;
      this.metrics.domInteractive = nav.domInteractive - nav.startTime;
    }
  }

  observe(type, callback) {
    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) callback(entry);
    });
    observer.observe({ type, buffered: true });
    this.observers.push(observer);
  }

  scheduleReport() {
    document.addEventListener('visibilitychange', () => {
      if (document.visibilityState === 'hidden') this.report();
    });
  }

  async report() {
    const payload = {
      url: location.href,
      timestamp: Date.now(),
      userAgent: navigator.userAgent,
      connection: navigator.connection
        ? { effectiveType: navigator.connection.effectiveType, downlink: navigator.connection.downlink }
        : null,
      metrics: this.metrics,
    };

    if (navigator.sendBeacon) {
      navigator.sendBeacon(this.endpoint, JSON.stringify(payload));
    } else {
      fetch(this.endpoint, { method: 'POST', body: JSON.stringify(payload), keepalive: true });
    }
  }

  destroy() {
    this.observers.forEach((o) => o.disconnect());
  }
}

const monitor = new PerformanceMonitor({ endpoint: '/api/vitals' });
monitor.start();

Core Web Vitals 全景

指標 フルネーム 閾値(良好/不良) 監視タイプ 重み
LCP Largest Contentful Paint ≤2.5s / >4s largest-contentful-paint 25%
INP Interaction to Next Paint ≤200ms / >500ms event 25%
CLS Cumulative Layout Shift ≤0.1 / >0.25 layout-shift 25%
FCP First Contentful Paint ≤1.8s / >3s paint 10%
TTFB Time to First Byte ≤800ms / >1800ms navigation 15%

パフォーマンスデータ送信の最適化

バッチ送信と圧縮

class MetricsBatcher {
  constructor(endpoint, options = {}) {
    this.endpoint = endpoint;
    this.batchSize = options.batchSize ?? 10;
    this.flushInterval = options.flushInterval ?? 5000;
    this.queue = [];
    this.timer = null;
  }

  enqueue(metric) {
    this.queue.push(metric);
    if (this.queue.length >= this.batchSize) this.flush();
    else if (!this.timer) {
      this.timer = setTimeout(() => this.flush(), this.flushInterval);
    }
  }

  flush() {
    if (this.timer) { clearTimeout(this.timer); this.timer = null; }
    if (this.queue.length === 0) return;

    const payload = JSON.stringify(this.queue);
    this.queue = [];

    if (navigator.sendBeacon) {
      navigator.sendBeacon(this.endpoint, payload);
    } else {
      fetch(this.endpoint, { method: 'POST', body: payload, keepalive: true });
    }
  }
}

ベストプラクティスまとめ

  1. buffered: trueを使用:ページ読み込み初期のパフォーマンスエントリを見逃さない
  2. INPはvisibilitychangeで送信:セッション内の最悪インタラクション値を取得
  3. 長時間タスクの帰属attributionで問題の原因(iframe/script)を特定
  4. sendBeaconを優先:ページアンロード時のデータ送信を保証
  5. INPの3つの遅延フェーズに注目:入力遅延、イベント処理、表示遅延をそれぞれ最適化

ブラウザローカルツールを無料で試す →

#Performance API#PerformanceObserver#Long Tasks#INP#性能监控