Referencia rápida de términos, acrónimos y conceptos relacionados con LoAF y web performance.
📋 Índice (click para expandir)
APIs y Métricas
LoAF (Long Animation Frames API)
API de Chrome 116+ que captura frames de animación lentos (>50ms) con attribution detallada de scripts y funciones responsables. A diferencia de Long Tasks API, LoAF proporciona información específica sobre qué función en qué archivo causó el problema.
Ejemplo:
{
duration: 264,
scripts: [{
sourceURL: "checkout.js",
sourceFunctionName: "validateCoupon",
duration: 218
}]
}
INP (Interaction to Next Paint)
Core Web Vital que mide la latencia de interacciones del usuario durante toda la vida de la página. Captura la peor interacción (P98) entre click, tap, y keyboard input.
Umbrales:
- 🟢 Good: ≤200ms
- 🟡 Needs Improvement: 200-500ms
- 🔴 Poor: >500ms
Por qué importa: Desde marzo 2024, INP es Core Web Vital oficial y puede afectar el ranking de búsqueda.
Referencias:
LCP (Largest Contentful Paint)
Core Web Vital que mide cuándo el contenido más grande se renderiza en el viewport. Típicamente es una imagen hero, video, o bloque de texto grande.
Umbrales:
- 🟢 Good: <2.5s
- 🟡 Needs Improvement: 2.5-4s
- 🔴 Poor: >4s
Relación con LoAF: Scripts que se ejecutan durante carga inicial pueden bloquear o retrasar LCP.
CLS (Cumulative Layout Shift)
Core Web Vital que mide estabilidad visual. Suma todos los layout shifts inesperados durante la vida de la página.
Umbrales:
- 🟢 Good: <0.1
- 🟡 Needs Improvement: 0.1-0.25
- 🔴 Poor: >0.25
FID (First Input Delay) [Deprecado]
Métrica anterior a INP que medía la latencia de la primera interacción solamente. Reemplazado por INP en marzo 2024.
Por qué se reemplazó: FID solo medía input delay de la primera interacción, ignorando el processing time y todas las interacciones subsecuentes.
TBT (Total Blocking Time)
Métrica de Lighthouse que suma el tiempo bloqueante de todas las Long Tasks entre FCP (First Contentful Paint) y TTI (Time to Interactive).
Relación con LoAF: TBT se calcula a partir de Long Tasks, mientras que LoAF proporciona más detalle (attribution).
Umbral: <200ms es considerado bueno en Lighthouse.
Event Timing API
API que captura métricas de latencia de eventos de usuario (click, keydown, etc.).
Qué proporciona:
- ✅ Tipo de evento (click, input, keypress)
- ✅ Duración total (input delay + processing + presentation)
- ✅ Target element
- ❌ No proporciona: Qué función/script causó la latencia
Uso típico: Calcular INP en producción.
new PerformanceObserver(list => {
for (const entry of list.getEntries()) {
if (entry.duration > 200) {
console.log("Slow interaction:", entry.name, entry.duration);
}
}
}).observe({ type: "event", buffered: true });
Long Tasks API
API (2017) que captura tareas que exceden 50ms en el main thread.
Qué proporciona:
- ✅ Duración de la tarea
- ✅ Timestamp de inicio
- ⚠️ Attribution limitada (solo container)
- ❌ No proporciona: Qué función específica, forzado de layout
Relación con LoAF: LoAF es la evolución de Long Tasks con attribution detallada.
Cuándo usar: Fallback para navegadores sin soporte de LoAF (Firefox, Safari).
Conceptos de Performance
Frame
Un ciclo completo de renderizado en el navegador:
- JavaScript execution
- Style calculation
- Layout calculation
- Paint
- Composite
Objetivo: 60fps = 16.67ms por frame (para animaciones suaves)
Long Animation Frame
Frame que excede 50ms de duración, capturado por LoAF API. El umbral de 50ms se eligió porque representa aproximadamente 3 frames a 60fps.
Por qué 50ms: Es el umbral donde los usuarios comienzan a percibir lag en interacciones.
Long Task
Tarea que excede 50ms en el main thread, capturada por Long Tasks API. Similar a Long Animation Frame pero sin attribution detallada.
Blocking Duration
Parte del frame que excede 50ms y bloquea interacciones del usuario. Es diferente de la duración total del frame.
Fórmula:
blockingDuration = Math.max(0, duration - 50);
Ejemplo:
- Frame duration: 150ms → Blocking duration: 100ms
- Frame duration: 40ms → Blocking duration: 0ms
Attribution
Información detallada sobre QUÉ código causó un problema de performance. Incluye:
- Nombre de función (
sourceFunctionName) - URL del archivo (
sourceURL) - Duración específica
- Forced layout duration
Antes de LoAF: Attribution manual en DevTools Con LoAF: Attribution automática en producción
Layout Thrashing / Forced Synchronous Layout
Anti-patrón donde alternas lecturas y escrituras del DOM en un bucle, forzando recálculos de layout costosos.
Ejemplo problemático:
for (let i = 0; i < 100; i++) {
const height = element.offsetHeight; // READ → fuerza layout
element.style.width = height + "px"; // WRITE → invalida layout
// Next iteration: layout recalculation needed again
}
// 100 iteraciones = 100 recálculos de layout
Solución:
// Batch reads
const measurements = elements.map(el => el.offsetHeight);
// Batch writes
elements.forEach((el, i) => (el.style.width = measurements[i] + "px"));
Referencias:
Forced Style and Layout Duration
Tiempo que un script gasta en recálculos forzados de style y layout. Expuesto por LoAF como forcedStyleAndLayoutDuration.
Causas comunes:
- Leer propiedades geométricas (
offsetHeight,clientWidth,getBoundingClientRect()) - Después de modificar el DOM
- Dentro de loops
Interpretación:
- 0ms → No hay forced layout (normal)
-
50ms → Posible layout thrashing
-
30% del script duration → Problema serio
Técnicas de Medición
RUM (Real User Monitoring)
Monitoreo de performance con datos de usuarios reales en producción, en contraste con testing sintético en ambiente controlado.
Ventajas:
- ✅ Refleja experiencia real
- ✅ Captura variedad de dispositivos/redes
- ✅ Detecta problemas que no aparecen en lab
Desventajas:
- ❌ Overhead en producción (mitigable con sampling)
- ❌ Privacy considerations
- ❌ Requiere infraestructura de backend
Synthetic Monitoring
Monitoreo con herramientas automatizadas (Lighthouse, WebPageTest) en ambiente controlado.
Ventajas:
- ✅ Reproducible
- ✅ Sin overhead en producción
- ✅ Fácil de configurar en CI/CD
Desventajas:
- ❌ No refleja usuarios reales
- ❌ Variabilidad entre runs
- ❌ Puede perder problemas específicos de producción
Sampling
Capturar solo un porcentaje de eventos para reducir overhead. Crítico para RUM en producción.
Ejemplo:
if (Math.random() < 0.1) {
// 10% sampling
captureLoafData(entry);
}
Reglas de oro:
- Alto tráfico (>1M visitas/mes): 1-5% sampling
- Tráfico medio (100K-1M): 5-10% sampling
- Bajo tráfico (<100K): 10-25% sampling
Por qué funciona: Con suficiente volumen, 10% de muestras es estadísticamente significativo.
Percentiles (P50, P75, P95, P99)
Estadísticas que muestran distribución de valores, más útiles que promedios para performance.
Definiciones:
- P50 (mediana): 50% de valores están por debajo
- P75: 75% de valores están por debajo (recomendado por Google para Core Web Vitals)
- P95: 95% de valores están por debajo (captura outliers sin incluir anomalías extremas)
- P99: 99% de valores están por debajo (usuarios más afectados)
Por qué no usar promedio:
// 90 frames de 75ms, 8 frames de 125ms, 2 frames de 800ms
avg = 91.5ms // ❌ Esconde los 2 frames problemáticos
P50 = 75ms // Experiencia típica
P75 = 75ms // Experiencia de mayoría
P95 = 800ms // ✅ Revela los outliers
Referencias:
Buffered Observations
Capturar entries que ocurrieron ANTES de que el observer se registrara. Crítico para capturar eventos durante page load.
observer.observe({
type: "long-animation-frame",
buffered: true, // ← Incluye entries anteriores
});
Sin buffered: true: Pierdes todos los frames que ocurrieron antes del observer.
Beacon
Envío de datos a servidor de forma no bloqueante usando navigator.sendBeacon().
Ventajas:
- ✅ No bloquea el main thread
- ✅ Garantiza envío incluso si usuario cierra la página
- ✅ No requiere esperar respuesta
Limitaciones:
- ❌ Payload máximo ~64KB
- ❌ Solo POST requests
- ❌ No puedes leer la respuesta
const data = JSON.stringify({ metrics: {...} });
const blob = new Blob([data], { type: 'application/json' });
navigator.sendBeacon('/api/analytics', blob);
Propiedades de LoAF
entry.duration
Duración total del frame (JavaScript + Rendering + overhead). Incluye todos los scripts y el rendering subsecuente.
const entry = performance.getEntriesByType("long-animation-frame")[0];
console.log(entry.duration); // ej: 264ms
entry.blockingDuration
Tiempo del frame que excede 50ms y bloquea interacciones del usuario.
blockingDuration = Math.max(0, duration - 50);
entry.renderStart
Timestamp cuando comienza el rendering (después de que JavaScript terminó).
Uso: Calcular cuánto tiempo se empleó en JavaScript vs rendering.
const jsTime = entry.renderStart - entry.startTime;
const renderTime = entry.duration - jsTime;
entry.styleAndLayoutStart
Timestamp cuando comienza el cálculo de style y layout.
entry.scripts
Array de scripts que ejecutaron durante el frame. Cada script tiene su propia attribution.
entry.scripts.forEach(script => {
console.log(script.sourceURL); // Archivo
console.log(script.sourceFunctionName); // Función
console.log(script.duration); // Tiempo
});
script.sourceURL
URL del archivo que contiene el script.
Casos comunes:
"https://example.com/app.js"→ Script externo normal"https://cdn.jsdelivr.net/npm/library@1.0.0/dist/bundle.js"→ Script desde CDN"/static/js/main.123.js"→ Script local con hash
Casos especiales:
""(vacío) → Script inline sin sourceURL"unknown"→ Script generado con eval() o new Function()"chrome-extension://..."→ Script de extensión de navegador
script.sourceFunctionName
Nombre de la función que se invocó.
Limitaciones:
""(vacío) → Función anónima o código global"a","b","Hc"→ Código minificado (nombres obfuscados)- Solo útil con código no minificado o con source maps
Implicación para producción: En scripts third-party minificados, este campo es casi inútil. Identifica por sourceURL (dominio) en su lugar.
script.duration
Tiempo que ese script específico consumió ejecutando JavaScript. No incluye rendering.
Diferencia con frame duration:
Frame duration: 200ms // Frame completo
Script duration: 120ms // Solo JavaScript de ese script
Difference: 80ms // Rendering + otros scripts + overhead
script.forcedStyleAndLayoutDuration
Tiempo que ese script gastó en recálculos forzados de style y layout (layout thrashing).
Interpretación:
0ms→ No hay forced layout (código limpio)>0ms pero <20% del script.duration→ Aceptable>30% del script.duration→ Layout thrashing severo
if (script.forcedStyleAndLayoutDuration > script.duration * 0.3) {
console.warn("Layout thrashing detected!");
}
script.invoker
Qué invocó el script. Puede ser un event handler, un timer, etc.
Ejemplos:
"IMG#hero.onload"→ Image load event"BUTTON#submit.onclick"→ Click event"Window.setTimeout"→ setTimeout callback"Window.requestAnimationFrame"→ rAF callback
Uso: Correlacionar script con interacción o evento específico.
Términos de Optimización
Code Splitting
Dividir código en chunks más pequeños que cargan on-demand, reduciendo el bundle inicial.
Ejemplo (Webpack):
import(/* webpackChunkName: "heavy" */ "./heavy-module.js").then(module =>
module.doSomething()
);
Ejemplo (Vite):
// Vite soporta dynamic imports nativamente
import("./heavy-module.js").then(module => module.doSomething());
// Con React + Vite
const HeavyComponent = lazy(() => import("./HeavyComponent"));
// Con Vue + Vite
const HeavyComponent = defineAsyncComponent(
() => import("./HeavyComponent.vue")
);
Lazy Loading
Cargar recursos solo cuando se necesitan, no durante la carga inicial.
Ejemplo (third-party script):
// ❌ Carga inmediata
<script src="analytics.js"></script>;
// ✅ Lazy load después de interacción
button.addEventListener(
"click",
() => {
import("./analytics.js").then(module => module.track(event));
},
{ once: true }
);
Debouncing
Retrasar ejecución de función hasta que pasen X milisegundos sin nuevos eventos.
Uso típico: Input field que dispara búsqueda.
const debounce = (fn, delay) => {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => fn(...args), delay);
};
};
input.addEventListener("input", debounce(search, 300));
Throttling
Limitar frecuencia de ejecución de función (máximo 1 vez cada X ms).
Uso típico: Scroll handler.
const throttle = (fn, limit) => {
let inThrottle;
return (...args) => {
if (!inThrottle) {
fn(...args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
};
window.addEventListener("scroll", throttle(onScroll, 100));
Memoization
Cachear resultados de funciones costosas para evitar recalcular.
const memoize = fn => {
const cache = new Map();
return (...args) => {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const result = fn(...args);
cache.set(key, result);
return result;
};
};
const expensiveCalc = memoize(n => {
// Cálculo costoso
return result;
});
FastDOM
Librería que batches lecturas y escrituras del DOM para evitar layout thrashing.
import fastdom from "fastdom";
// Batch reads
fastdom.measure(() => {
const height = element.offsetHeight;
// Batch writes
fastdom.mutate(() => {
element.style.width = height + "px";
});
});
Referencias:
requestIdleCallback
API para ejecutar código durante periodos idle del navegador, sin afectar performance.
requestIdleCallback(deadline => {
while (deadline.timeRemaining() > 0 && tasks.length > 0) {
const task = tasks.pop();
task();
}
});
Cuidado: No soportado en Safari (usar polyfill).
requestAnimationFrame
API para sincronizar código con refresh rate del display (típicamente 60fps = ~16.67ms).
Uso típico: Animaciones suaves.
function animate() {
// Update animation
element.style.transform = `translateX(${x}px)`;
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
Scripts y Orígenes
First-party Script
Script del mismo dominio que la página.
Ejemplo:
- Página:
https://example.com/page - Script:
https://example.com/app.js✅ First-party
Third-party Script
Script de dominio diferente a la página.
Ejemplos comunes:
- Analytics:
https://www.google-analytics.com/analytics.js - Ads:
https://securepubads.g.doubleclick.net/tag/js/gpt.js - CDN:
https://cdn.jsdelivr.net/npm/library@1.0.0/dist/bundle.js
Estadística: Los third-party scripts tienen un impacto significativo en performance, bloqueando el main thread en hasta 90% de páginas móviles. Fuente: HTTP Archive Web Almanac 2022
Inline Script
Código JavaScript dentro de <script> sin atributo src.
<script>
console.log("Inline script");
</script>
En LoAF: sourceURL estará vacío o será el URL de la página.
External Script
Código JavaScript cargado desde archivo externo vía <script src="">.
<script src="/app.js"></script>
Minified Code
Código comprimido eliminando espacios, comentarios, y renombrando variables.
Ejemplo:
// Original
function calculateTotalPrice(items) {
return items.reduce((sum, item) => sum + item.price, 0);
}
// Minificado
function a(b) {
return b.reduce((c, d) => c + d.price, 0);
}
Impacto en LoAF: sourceFunctionName será críptico (a, b, Hc).
Source Maps
Archivos que mapean código minificado a código original para debugging.
Formato: app.min.js.map
En DevTools: Permite ver código original incluso con bundle minificado.
Limitación con LoAF: La API nativa de LoAF NO procesa Source Maps automáticamente. En producción devuelve nombres minificados (sourceFunctionName: "a"). Solo DevTools correlaciona Source Maps para mostrar nombres originales cuando está disponible.
Workaround para RUM: Herramientas como Sentry pueden procesar Source Maps del lado servidor para mapear stack traces de LoAF a código original.
Herramientas
DevTools Performance Panel
Panel de Chrome DevTools para grabar y analizar performance con flamegraphs, timeline, y métricas.
Acceso:
- Windows/Linux:
F12oCtrl+Shift+I→ Performance tab - macOS:
Cmd+Option+I→ Performance tab
Lighthouse
Herramienta de auditoría automatizada de Google. Mide performance, accessibility, SEO, best practices.
Acceso:
- Chrome DevTools → Lighthouse tab
- CLI:
npm install -g lighthouse - CI/CD: Lighthouse CI
Métricas:
- Performance Score (0-100)
- Core Web Vitals (LCP, CLS, TBT)
- Opportunities y Diagnostics
WebPageTest
Herramienta online para testing de performance con dispositivos reales y múltiples ubicaciones.
URL: webpagetest.org
Ventajas:
- Testing desde dispositivos reales
- Múltiples ubicaciones geográficas
- Filmstrip view, waterfall, etc.
Chrome User Experience Report (CrUX)
Dataset público con métricas de usuarios reales de Chrome (field data).
Acceso:
- CrUX Dashboard
- BigQuery:
chrome-ux-report.* - PageSpeed Insights API
Web Vitals Extension
Extension de Chrome que muestra Core Web Vitals en tiempo real en un HUD overlay.
Instalación: Chrome Web Store
Referencias Rápidas
Umbrales Críticos
Core Web Vitals (Umbrales Oficiales de Google)
| Métrica | Good | Needs Improvement | Poor | Fuente |
|---|---|---|---|---|
| INP | ≤200ms | 200-500ms | >500ms | web.dev/inp |
| LCP | ≤2.5s | 2.5-4s | >4s | web.dev/lcp |
| CLS | ≤0.1 | 0.1-0.25 | >0.25 | web.dev/cls |
Lighthouse Metrics (Métricas Sintéticas)
| Métrica | Good | Needs Improvement | Poor |
|---|---|---|---|
| TBT | ≤200ms | 200-600ms | >600ms |
LoAF Frames (Recomendaciones Prácticas - No Oficiales)
⚠️ Nota: Los siguientes umbrales son recomendaciones prácticas para clasificar frames de LoAF, no son métricas oficiales de Google. Usa estos valores como guía para priorizar optimizaciones.
| Concepto | Good | Medium | High | Critical |
|---|---|---|---|---|
| Frame duration | ≤50ms | 50-100ms | 100-200ms | >200ms |
| Blocking duration | 0ms | <50ms | 50-100ms | >100ms |
Justificación de umbrales de frame:
- ≤50ms: No se considera “long frame” por LoAF (umbral mínimo)
- 50-100ms: Frame lento pero manejable (~1-2 frames a 60fps)
- 100-200ms: Frame lento que puede contribuir a INP >200ms
- >200ms: Frame crítico, puede causar INP poor por sí solo
Compatibilidad
| API | Chrome | Edge | Firefox | Safari |
|---|---|---|---|---|
| LoAF | 116+ ✅ | 116+ ✅ | ❌ | ❌ |
| Long Tasks | 58+ ✅ | 79+ ✅ | ❌ | ❌ |
| Event Timing | 76+ ✅ | 79+ ✅ | 89+ ✅ | ❌ |
| PerformanceObserver | 52+ ✅ | 79+ ✅ | 57+ ✅ | 11+ ✅ |
Cobertura LoAF: ~82% usuarios globales (Chrome + Edge)
APIs Relacionadas (Cronología)
| Año | API | Propósito |
|---|---|---|
| 2012 | Navigation Timing | Métricas de navegación |
| 2013 | Resource Timing | Timing de recursos |
| 2015 | User Timing | Custom marks y measures |
| 2017 | Long Tasks API | Detectar tareas >50ms |
| 2019 | Event Timing | Latencia de eventos |
| 2019 | Layout Instability | Medir CLS |
| 2019 | Largest Contentful Paint | Medir LCP |
| 2023 | Long Animation Frames API | Attribution detallada ⭐ |
Recursos de Aprendizaje
Documentación Oficial
- Chrome Docs: LoAF
- MDN: PerformanceLongAnimationFrameTiming
- W3C: Long Animation Frames Spec
- Web.dev: Optimize INP
Artículos y Guías
- DebugBear: Measuring Long Animation Frames
- MDN: Performance API
- The Definitive Guide to Long Animation Frames (LoAF)
- Web.dev: Core Web Vitals
- Web.dev: User-centric performance metrics
Herramientas y Recursos
- Can I Use: LoAF
- Paul Irish: What forces layout
- Creates custom DevTools Performance Panel populated with entries from the Performance Timeline
Casos de Éxito
Taboola: Optimización de INP con técnicas avanzadas
- Caso: Cómo Taboola redujo INP en un 50%
- Contexto: Plataforma de contenido web optimizando interacciones en widgets
- Estrategias: Análisis profundo de long tasks, optimización de event handlers, mejoras en rendering
- Impacto: Reducción del 50% en INP y mejoras significativas en user engagement
Recursos adicionales de casos reales:
- Web.dev Case Studies - Colección de casos reales de optimización
- Google Developers Showcase - Ejemplos de implementaciones exitosas
- SpeedCurve Case Studies - Análisis de performance en sitios reales