Long Animation Frames API 2026: az INP problémák felderítése LoAF-fal

A Long Animation Frames API pontosan megmondja, melyik szkript, layout vagy stílusszámítás okozza az INP lassulást. Működő kód, RUM integráció és böngészőtámogatás 2026-ban.

Frissítve: 2026. augusztus 20.

A Long Animation Frames API (röviden LoAF) egy 2024-ben véglegesített böngészős mérőeszköz, amely megmutatja, pontosan melyik szkript, layout vagy stílusszámítás okozta, hogy egy renderelési frame 50 ms-nál tovább tartott. Így közvetlenül azonosítható az INP-lassulások gyökere. A korábbi Long Task API csak azt mondta meg, hogy „valami" beragadt; a LoAF viszont megnevezi a függvényt, a forgatókönyvet, a script URL-t, sőt még azt is, hogy melyik iframe futtatta. Ha az INP-metrika 200 ms fölé csúszik és nincs sejtésed, hol veszted az időt a fő szálon, ez az API az, amit keresel.

  • A Long Animation Frames API-t a Chrome 123 óta lehet éles környezetben használni, és 2026-ban már a Chromium-alapú böngészők (Chrome, Edge, Opera, Brave) mindegyike támogatja.
  • Egy „long animation frame" olyan renderelési ciklus, amely legalább 50 ms-ig tart. A specifikáció szerint ez az a küszöb, ahonnan a felhasználó észreveszi az akadozást.
  • A LoAF a Long Task API-hoz képest attribúciót ad: script neve, invoker típus, karakterpozíció, forrás iframe és a blokkoló időtartam külön van bontva.
  • A PerformanceObserver API-n keresztül kell feliratkozni, és a renderStart, styleAndLayoutStart, valamint a scripts tömb adja a diagnózishoz szükséges részleteket.
  • A web-vitals.js 4.x már natívan használja a LoAF-ot az onINP attribution módban, hogy a leglassabb interakcióhoz csatolja a felelős szkriptet.
  • A fallback stratégia egyszerű: ha a PerformanceObserver.supportedEntryTypes nem tartalmazza a long-animation-frame értéket, essünk vissza a Long Task API-ra.

Mi az a Long Animation Frames API?

A Long Animation Frames API egy alacsony költségű megfigyelő interfész, amely minden olyan renderelési frame-ről bejegyzést készít, aminek teljes időtartama meghaladja a 50 ms-ot. A specifikáció szerint egy „animation frame" a böngészőnek attól a pillanattól tart, amikor elkezdi a rendering ciklus előkészítését, egészen addig, míg a képernyőre került az új képkocka. Ha ez az intervallum hosszabb 50 ms-nál, az entry a PerformanceObserver-be kerül. A 50 ms-os küszöb nem véletlen: a Response Time Guidelines szerint ez az a határ, ahonnan a felhasználó „nem azonnalinak" érzékeli a reakciót, és ez pontosan az a tartomány, ahol az INP metrika elkezd 200 ms fölé csúszni.

A régi Long Task API csak egyetlen dimenziót adott: „valami 50 ms-nál tovább tartott a főszálon". A LoAF ezzel szemben minden entry-hez visszaadja a renderStart, styleAndLayoutStart, firstUIEventTimestamp, blockingDuration és a scripts tömb mezőit. Ebből kiolvasható, hogy a frame melyik része volt a szkriptvégrehajtás, melyik a stílusszámítás és a layout, melyik pedig a paint. Ez az én workflow-mban azt jelenti, hogy nem kell többet console.time-mal bombázni a kódbázist annak érdekében, hogy megtudjam, melyik listener okoz akadást. A böngésző megmondja helyettem.

Long Animation Frames vs. Long Task API

A két API-t gyakran összekeverik, mert mindkettő ugyanazt az 50 ms-os küszöböt használja. A különbség azonban jelentős, és 2026-ban gyakorlatilag már nincs okod a Long Task API-t használni új projektben. Az alábbi táblázat összegzi a fő eltéréseket, amiket egy tényleges profilozási session során tapasztaltam:

JellemzőLong Task APILong Animation Frames API
Küszöb50 ms egy task-ra50 ms egy teljes frame-re
AttribúcióCsak a container (iframe) neveScript URL, függvény, karakterpozíció, invoker típus
Blocking durationNem elérhetőKülön mező (blockingDuration)
Render lifecycleNem láthatórenderStart, styleAndLayoutStart, paint elkülönítve
UI esemény időbélyegNincsfirstUIEventTimestamp (INP debug)
Böngésző (2026)Chromium, Safari 16.4+Chromium 123+ (Chrome, Edge, Opera, Brave)
OverheadAlacsonyAlacsony (csak > 50 ms frame-ek)

A gyakorlatban ez a különbség a következőt jelenti. A Long Task API-val tudom, hogy egy 320 ms-os task megakasztotta a felhasználót; a LoAF-fal viszont azt is, hogy ez a 320 ms 240 ms scriptvégrehajtás plusz 60 ms layout plusz 20 ms paint volt, és a script a https://cdn.example.com/analytics-v3.js 8412. karakterénél kezdődő függvény. Ez órákat spórol meg egy debugging session során. Őszintén szólva ezért is dobtam ki a régi Long Task hook-jaimat egy nagyobb SPA-nál még tavaly.

Hogyan használjuk a LoAF-ot a gyakorlatban?

A legegyszerűbb integrációhoz egy PerformanceObserver-t regisztrálunk a long-animation-frame típusra, majd feldolgozzuk az entry-ket. Ez a snippet lokálisan is elfut, és a konzolban részletes attribúciót ír ki minden 50 ms fölötti frame-hez:

// LoAF observer teljes attribúcióval
const loafObserver = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.group(`LoAF frame: ${entry.duration.toFixed(1)} ms`);
    console.log('Blocking:', entry.blockingDuration.toFixed(1), 'ms');
    console.log('Render start delay:',
      (entry.renderStart - entry.startTime).toFixed(1), 'ms');
    console.log('Style + layout:',
      (entry.styleAndLayoutStart
        ? entry.duration - (entry.styleAndLayoutStart - entry.startTime)
        : 0).toFixed(1), 'ms');

    // A script attribúció itt kezdődik
    for (const script of entry.scripts) {
      console.log(
        `[${script.invokerType}]`,
        script.invoker,
        '→',
        script.sourceURL,
        `${script.sourceCharPosition}`,
        `(${script.duration.toFixed(1)} ms)`
      );
    }
    console.groupEnd();
  }
});

loafObserver.observe({
  type: 'long-animation-frame',
  buffered: true // már meglévő entry-k is
});

A buffered: true opció azt biztosítja, hogy az observer regisztrálása előtt keletkezett entry-ket is megkapod. Ez különösen hasznos, ha a mérőkódot lazy módon töltöd. Az invokerType mező hét lehetséges értéket vehet fel: user-callback, event-listener, resolve-promise, reject-promise, classic-script, module-script, console-log. Az én tapasztalatom szerint a event-listener és a resolve-promise a leggyakoribb bűnös produkciós site-okon (általában egy vastag click handler vagy egy async függvény, ami tömbök százain iterál).

Harmadik fél szkriptek felderítése LoAF-fal

A produkciós teljesítményproblémák jelentős része harmadik fél szkriptekből ered: analytics, tag manager, chat widget, A/B teszt SDK. A régi Long Task API csak azt mondta meg, hogy egy iframe csinálja, de a modern világban ezek a szkriptek gyakran közvetlenül a fő dokumentumban futnak. A LoAF sourceURL mezője viszont pontos attribúciót ad. Ez különösen jól illeszkedik a harmadik fél szkriptek optimalizálási stratégiájához, mert számokkal igazolható, hogy egy adott vendor SDK valóban rontja-e az INP-t.

Az alábbi kód egy production-safe aggregátor, amit a saját RUM pipeline-omban használok. Csak azokat a frame-eket küldi vissza, amelyek harmadik fél szkriptet érintenek, és domain szinten csoportosítja őket:

// Third-party szkript attribúció aggregálása
const FIRST_PARTY = new Set([
  location.hostname,
  'cdn.mysite.com',
  'assets.mysite.com'
]);

const thirdPartyCost = new Map(); // domain → total blocking ms

new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    for (const script of entry.scripts) {
      if (!script.sourceURL) continue;
      let host;
      try {
        host = new URL(script.sourceURL).hostname;
      } catch { continue; }
      if (FIRST_PARTY.has(host)) continue;

      const prev = thirdPartyCost.get(host) ?? 0;
      thirdPartyCost.set(host, prev + script.duration);
    }
  }
}).observe({ type: 'long-animation-frame', buffered: true });

// 30 másodpercenként riportolás a saját endpointra
setInterval(() => {
  if (!thirdPartyCost.size) return;
  navigator.sendBeacon('/rum/loaf-thirdparty', JSON.stringify(
    Array.from(thirdPartyCost.entries())
      .map(([host, ms]) => ({ host, ms: Math.round(ms) }))
      .sort((a, b) => b.ms - a.ms)
  ));
  thirdPartyCost.clear();
}, 30_000);

A sendBeacon használata itt kritikus: ez az API akkor is elküldi az adatot, ha a felhasználó közben navigál el az oldalról. A Google Tag Manager-től eredő entry-k például általában a www.googletagmanager.com/gtm.js URL alatt jelennek meg, míg a Facebook Pixel a connect.facebook.net alatt. Ez alapján a marketing csapat felé is dokumentálható, hogy melyik tag mennyi milliszekundumba kerül a felhasználónak. (Ezt a riportot egy ügyfelemnél sikerült úgy elfogadtatni, hogy három tag-et végül kidobtak.)

LoAF integráció a web-vitals.js-szel és RUM-mal

A web-vitals.js 4.x már natívan integrálja a LoAF-ot: az onINP callback attribution módban visszaadja a leglassabb interakcióhoz tartozó LoAF entry-ket, így egyetlen mérésből mind a metrika (INP érték), mind a diagnózis (melyik script okozta) megvan. Ez egyben a legegyszerűbb módja annak, hogy a saját RUM pipeline-odba beleépítsd a LoAF adatokat:

import { onINP } from 'web-vitals/attribution';

onINP(({ value, attribution }) => {
  const payload = {
    inp: Math.round(value),
    interactionTarget: attribution.interactionTarget,
    // LoAF-ból származó adatok
    loafScripts: (attribution.longAnimationFrameEntries ?? [])
      .flatMap((f) => f.scripts)
      .map((s) => ({
        url: s.sourceURL,
        invoker: s.invoker,
        ms: Math.round(s.duration),
        forcedStyle: Math.round(s.forcedStyleAndLayoutDuration ?? 0)
      }))
      .filter((s) => s.ms > 10) // zaj kiszűrése
      .slice(0, 5),               // top 5 script
  };
  navigator.sendBeacon('/rum/inp', JSON.stringify(payload));
}, { reportAllChanges: false });

A forcedStyleAndLayoutDuration mező különösen értékes: ez mutatja meg, ha egy szkript szinkron layout-ot kényszerített ki (pl. element.offsetHeight-ot olvasott közvetlenül DOM módosítás után). Ez a klasszikus „layout thrashing" mintázat, amit korábban csak a Chrome DevTools Performance panelben lehetett észrevenni. Éles környezetben most bármelyik felhasználó gépén detektálható, és ez a fajta adat vezetett minket egy közösen fejlesztett projekten már többször is ahhoz a felismeréshez, hogy egy pici debounce vagy egy requestAnimationFrame wrap oldotta meg a lassulást.

Böngészőtámogatás és fallback stratégia

2026 augusztusában a helyzet a következő. A Chromium-alapú böngészők (Chrome 123+, Edge 123+, Opera 109+, Brave, Arc, Samsung Internet 24+) mindegyike támogatja a Long Animation Frames API-t. A Safari (WebKit) és a Firefox jelenleg nem implementálja. A WebKit oldalán a spec „In Development" státuszban van, a Firefox-nál pedig még a dom.performance.long_animation_frame.enabled flag mögé sincs beépítve production build-ben.

Ez a valóságban nem probléma: az INP metrika 96%-ban Chromium felhasználóktól származik a CrUX 2026 Q2 adatai szerint, tehát a LoAF pontosan azon a platformon működik, ahol a legtöbb debug jelet szeretnénk kapni. A fallback logika viszont fontos, hogy ne dobjunk hibát Safari-n vagy Firefox-on:

// Feature detection + fallback
function observeLongFrames(callback) {
  const types = PerformanceObserver.supportedEntryTypes ?? [];

  if (types.includes('long-animation-frame')) {
    // Elsődleges: LoAF (Chromium)
    new PerformanceObserver((list) => {
      for (const e of list.getEntries()) {
        callback({
          source: 'loaf',
          duration: e.duration,
          blocking: e.blockingDuration,
          scripts: e.scripts.map((s) => ({
            url: s.sourceURL,
            ms: s.duration
          }))
        });
      }
    }).observe({ type: 'long-animation-frame', buffered: true });
    return;
  }

  if (types.includes('longtask')) {
    // Fallback: Long Task (Safari, régebbi böngészők)
    new PerformanceObserver((list) => {
      for (const e of list.getEntries()) {
        callback({
          source: 'longtask',
          duration: e.duration,
          blocking: e.duration - 50,
          scripts: [] // nem elérhető
        });
      }
    }).observe({ type: 'longtask', buffered: true });
    return;
  }

  // Se LoAF, se Long Task, csendben nem csinálunk semmit
}

observeLongFrames((data) => {
  // Egységes riportolás
});

Gyakori hibák LoAF elemzésnél

Az elmúlt évben több csapatnál is segítettem bevezetni a LoAF-ot, és három visszatérő hiba fordul elő, amelyek elrontják az elemzést. Az első, hogy a fejlesztők a duration mezőt egyenlővé teszik a „káros idővel", pedig ez tartalmazza a paint és compositing időt is, ami elkerülhetetlen. A tényleges „elkerülhető" idő a blockingDuration, ami csak azt méri, mennyivel volt hosszabb a frame a következő paint lehetőségnél. A második hiba, hogy csak a scripts[0]-t nézik. Egy 200 ms-os frame gyakran öt-hat script kombinációja, és a legnagyobb blokkoló nem mindig az első a tömbben. A helyes megközelítés a duration szerinti csökkenő sorrend.

A harmadik és talán legalattomosabb hiba a cross-origin szkriptek maszkolása. Ha egy külső domain szkriptje nem küld Timing-Allow-Origin: * fejlécet, a sourceURL és a sourceCharPosition mezők üresek lesznek (ez látszik invokerType: "classic-script" mellett is). Ilyenkor a vendor felé kell menni, hogy állítsák be a fejlécet, vagy proxyzd magadhoz a szkriptet. A Chrome for Developers LoAF dokumentációja részletesen leírja ezt az attribúciós modellt, és példát is ad a leggyakoribb SDK-król. (Anno én pont a Segment analytics szkriptjén akadtam el, mire rájöttem, hogy a TAO fejléc hiányzik.)

Végül egy figyelmeztetés. A fejlesztői környezetben (localhost, dev build) a LoAF adatok szinte használhatatlanul zajosak, mert a React DevTools, a Vite HMR és a source map betöltés mind extra frame-eket generál. A LoAF-ot mindig production build-en vagy legalább preview build-en profilozd, lehetőleg mid-range mobilon (Moto G Power vagy Pixel 6a) CPU throttling nélkül, mert a throttling maga is torzítja a blockingDuration értéket. Ezt egyébként az LCP optimalizálás gyakorlati útmutatóban is részleteztem az eszközválasztás kapcsán.

Gyakran Ismételt Kérdések

Mi a különbség a Long Animation Frame és egy Long Task között?

A Long Task egyetlen JavaScript feladatot mér, ami 50 ms-nál tovább tartott. A Long Animation Frame egy teljes renderelési ciklust mér (több task, layout, style és paint összesen), és részletes attribúciót ad script URL, invoker típus és blocking duration szinten.

Melyik böngészők támogatják a Long Animation Frames API-t 2026-ban?

A Chromium-alapú böngészők (Chrome 123+, Edge 123+, Opera, Brave, Arc, Samsung Internet 24+) mindegyike támogatja. A Safari és a Firefox 2026 augusztusában még nem implementálja, de a WebKit oldalán már „In Development" státuszban van.

Mekkora overhead-je van a LoAF observer-nek?

Minimális, mert csak azok a frame-ek keletkeztetnek entry-t, amelyek meghaladják az 50 ms-ot. Egy jól optimalizált oldalon percek is eltelhetnek egyetlen entry között, tehát a memória- és CPU-hatás elhanyagolható. Produkciós RUM pipeline-ban is biztonságosan használható.

Miért üres a sourceURL egyes script bejegyzéseknél?

Ez akkor fordul elő, ha a szkript cross-origin, és a szerver nem küld Timing-Allow-Origin: * fejlécet. A böngésző biztonsági okokból elrejti az URL-t. Kérj a vendortól TAO fejlécet, vagy proxyzd magadhoz a fájlt.

Használhatom a LoAF-ot INP debug-hoz éles környezetben?

Igen, ez pontosan az elsődleges use case-e. A web-vitals.js 4.x onINP attribution módja már automatikusan csatolja a leglassabb interakcióhoz tartozó LoAF entry-ket, így minden 200 ms fölötti INP mérés mellé megkapod a felelős script URL-jét és a blocking duration-t.

Alex Petrov
A Szerzőről Alex Petrov

Web performance engineer who treats every millisecond as a personal challenge. Has profiled more sites than he can count.