Ejemplo 05: Script causando INP alto en botón

← Volver al índice

Combinando Event Timing API con LoAF

Hemos visto que Event Timing API nos dice cuándo una interacción fue lenta, y LoAF API nos dice qué scripts se ejecutaron en un frame lento.

Pero, ¿cómo correlacionamos ambas APIs para saber exactamente qué script causó que una interacción específica (un click, por ejemplo) fuera lenta?

Este ejemplo muestra cómo combinar ambas APIs para obtener el diagnóstico completo.

Demo interactiva

📊 Diagnóstico completo:

Análisis de la interacción:

Cómo funciona la correlación

1. Detectar interacción lenta (Event Timing API)

const eventObserver = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.duration > 200) {
      // Interacción lenta detectada
      findRelatedLoaf(entry);
    }
  }
});

eventObserver.observe({ type: 'event', buffered: true });

2. Buscar el LoAF correspondiente

function findRelatedLoaf(eventEntry) {
  // Obtener todos los LoAFs
  const loafs = performance.getEntriesByType('long-animation-frame');

  // Buscar el que se superpone con el evento
  const relevantLoaf = loafs.find(loaf => {
    const loafEnd = loaf.startTime + loaf.duration;
    const eventEnd = eventEntry.startTime + eventEntry.duration;

    // ¿El LoAF y el evento se superponen en tiempo?
    return loaf.startTime <= eventEnd && loafEnd >= eventEntry.startTime;
  });

  return relevantLoaf;
}

3. Identificar el script culpable

if (relevantLoaf) {
  console.log('Interacción lenta causada por:');

  relevantLoaf.scripts.forEach(script => {
    console.log('  - Función:', script.sourceFunctionName);
    console.log('    Archivo:', script.sourceURL);
    console.log('    Duración:', script.duration + 'ms');
  });
}
✅ El resultado:

Ahora sabemos exactamente qué función, en qué archivo, causó que este click específico fuera lento. No hay ambigüedad.

Ventajas de la correlación

Enfoque Información
Solo Event Timing "El click tardó 250ms" ❌ ¿Por qué?
Solo LoAF "handleClick() tardó 200ms" ❌ ¿Afectó al usuario?
Event Timing + LoAF "El click tardó 250ms porque handleClick() tardó 200ms" ✅ Completo

Casos de uso prácticos

Debugging de INP en producción

// Capturar interacciones lentas con contexto completo
const eventObserver = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.duration > 200) {
      const loaf = findRelatedLoaf(entry);

      // Enviar a analytics con contexto completo
      sendToAnalytics({
        metric: 'slow_interaction',
        interactionType: entry.name,
        duration: entry.duration,
        targetElement: entry.target?.tagName,
        scripts: loaf?.scripts.map(s => ({
          function: s.sourceFunctionName,
          url: s.sourceURL,
          duration: s.duration
        }))
      });
    }
  }
});

Alertas automáticas

if (entry.duration > 500 && relevantLoaf) {
  // Interacción crítica: alertar al equipo
  sendSlackAlert({
    message: `INP crítico detectado: ${entry.duration}ms`,
    element: entry.target?.id,
    culprit: relevantLoaf.scripts[0]?.sourceFunctionName,
    url: window.location.href,
    userAgent: navigator.userAgent
  });
}
⚠️ Importante:

La correlación temporal no siempre es 1:1. Un evento puede disparar múltiples LoAFs, o un LoAF puede contener scripts de múltiples eventos. Siempre verifica la lógica de overlapping.

Performance de la correlación

Buscar en el array de LoAFs es eficiente porque:

Para optimizar aún más:

// Cachear los últimos LoAFs en memoria
const recentLoafs = [];
const MAX_LOAF_CACHE = 50;

const loafObserver = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    recentLoafs.push(entry);
    if (recentLoafs.length > MAX_LOAF_CACHE) {
      recentLoafs.shift(); // Eliminar el más antiguo
    }
  }
});

// Buscar solo en el cache
function findRelatedLoaf(eventEntry) {
  return recentLoafs.find(loaf => overlaps(loaf, eventEntry));
}