Nazad na blog

Core Web Vitals za animirane sajtove: LCP, INP i CLS

Core Web Vitals za animirane sajtove: LCP, INP i CLS

6. oktobar 2026. — Performanse

Objavljeno

6. oktobar 2026.

Vreme čitanja

4 min čitanja

Kategorija

Performanse

Oznake

  • Core Web Vitals
  • Performance
  • Animation
  • WebGL
  • LCP
  • INP
  • CLS

(Uvod)Kako da zadržite animacije pri skrolovanju, WebGL i video na sajtu, a ipak prođete Core Web Vitals. Konkretni uzroci sporog LCP-a, INP-a i CLS-a i rešenja.

Animirani sajtovi imaju reputaciju sporih, i često je zaslužuju. Ali pokret i dobri Core Web Vitals nisu suprotnosti. Većina problema dolazi sa kratkog spiska uzroka, a za svaki postoji rešenje koje zadržava animaciju.

Ovaj sajt ima video u zaglavlju, WebGL zid sa projektima, WebGL tipografiju i animacije pri skrolovanju svuda. Ovo su pravila na kojima je napravljen.

Tri metrike, ukratko

Metrika Šta meri Dobro
LCP — Largest Contentful Paint Kada se pojavi glavni sadržaj ≤ 2,5 s
INP — Interaction to Next Paint Koliko brzo stranica reaguje na dodire i klikove ≤ 200 ms
CLS — Cumulative Layout Shift Koliko raspored poskakuje ≤ 0,1

Google ih ocenjuje na 75. percentilu stvarnih Chrome korisnika (terenski podaci u PageSpeed Insights i Search Console), a ne na osnovu Lighthouse testa na vašem laptopu. Testirajte na Android telefonu srednje klase uz usporenu vezu; to je bliže vašem 75. percentilu od MacBooka.

LCP: pokažite nešto stvarno, rano

Ne skrivajte naslovnu sekciju iza ulazne animacije

Najčešći ubica LCP-a na animiranim sajtovima je naslovna sekcija koja počinje sa opacity: 0 i pojavljuje se tek kada se učita JavaScript. Pregledač ne može da je računa kao iscrtanu dok nije vidljiva, pa LCP čeka vaš bundle, a zatim i animaciju.

Rešenje: renderujte konačno stanje naslovne sekcije u HTML-u i CSS-u. Animirajte sporedne elemente, ili animirajte iz stanja koje je već vidljivo (blago skaliranje ili pomeranje, ne iz nulte prozirnosti).

Dajte LCP elementu poster i prioritet

Ako je naslovna sekcija video ili WebGL platno, LCP kandidat je obično njegova poster slika. Neka bude mala, unapred učitana i visokog prioriteta:

html
<link rel="preload" as="image" href="/hero/poster.avif" fetchpriority="high" />
<img src="/hero/poster.avif" alt="" fetchpriority="high" width="1600" height="900" />

Video ili WebGL scenu pokrenite nakon što se poster iscrta, i pređite na nju postepeno.

Učitavajte teške biblioteke kada su potrebne

Three.js, GSAP pluginovi i Lottie ne moraju da budu u prvom bundle-u ako pokreću sekcije ispod prvog ekrana. Uvezite ih dinamički kada se sekcija približi vidljivom delu:

ts
const observer = new IntersectionObserver(async ([entry]) => {
  if (!entry.isIntersecting) return
  observer.disconnect()
  const { initScene } = await import('./scene')
  initScene(canvas)
}, { rootMargin: '400px 0px' })
observer.observe(section)

U Next.js, next/dynamic sa ssr: false radi isto za cele komponente.

Poslužujte medije odgovarajuće veličine

Video od 1440p na telefonu je bačen protok koji se takmiči sa vašom LCP slikom. Birajte izvore prema širini ekrana i vezi, a video petlje u zaglavlju neka budu kratke (6–10 sekundi, bez audio zapisa).

INP: oslobodite glavnu nit

INP meri kašnjenje između interakcije i sledećeg kadra. Animacija mu šteti kada je JavaScript zauzet u svakom kadru.

Animirajte samo transform i opacity

Njima upravlja compositor i ne izazivaju ponovni raspored. Animiranje top, left, width, height ili margin primorava raspored u svakom kadru:

css
/* Slow: layout every frame */
.card { transition: top 0.3s; }

/* Fast: compositor only */
.card { transition: transform 0.3s; }

Ne čitajte raspored unutar scroll handlera

getBoundingClientRect() posle promene stila primorava sinhroni raspored. U scroll handlerima čitajte jednom po kadru unutar requestAnimationFrame, a zatim pišite:

ts
let ticking = false
window.addEventListener('scroll', () => {
  if (ticking) return
  ticking = true
  requestAnimationFrame(() => {
    const top = section.getBoundingClientRect().top   // read
    progressBar.style.transform = `scaleX(${clamp(-top / range)})` // write
    ticking = false
  })
}, { passive: true })

Još bolje, prepustite to biblioteci kao što je GSAP-ov ScrollTrigger, ili koristite CSS animacije vođene skrolovanjem (animation-timeline: scroll()) gde su podržane.

Zaustavite posao koji se ne vidi

Svaka requestAnimationFrame petlja na stranici radi na glavnoj niti. Pauzirajte platna, video zapise i petlje kada izađu iz vidljivog dela i kada je kartica skrivena:

ts
const io = new IntersectionObserver(([e]) => (e.isIntersecting ? start() : stop()))
io.observe(canvas)
document.addEventListener('visibilitychange', () => (document.hidden ? stop() : start()))

Ograničite rezoluciju platna

WebGL platno sa devicePixelRatio 3 na telefonu iscrtava devet puta više piksela nego pri DPR 1. Ograničite ga:

ts
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))

Za efekte preko celog ekrana, poput magle ili šuma, renderovanje u pola rezolucije pa uvećavanje često se ne razlikuje od originala.

Razbijte duge zadatke

Pravljenje scene, parsiranje velikog JSON-a ili pripremanje mnogo tekstualnih mreža može blokirati nit stotinama milisekundi. Podelite posao i prepustite nit između delova (await scheduler.yield() gde je dostupno, inače setTimeout(0)) kako bi dodir tokom pripreme dobio brz odgovor.

CLS: rezervišite prostor za sve

Dajte medijima dimenzije

Svaka slika, video, iframe i platno treba da ima atribute width/height ili CSS aspect-ratio, kako bi pregledač rezervisao prostor pre učitavanja.

Učitavajte fontove bez skoka

Dekorativni fontovi su deo karaktera animiranog sajta, a kasna zamena fonta pomera svaki red. Unapred učitajte jedan ili dva ključna fajla, smanjite ih na potrebne karaktere i koristite font-display: swap sa rezervnim fontom podešenih metrika (size-adjust, ascent-override) — next/font to generiše automatski.

Animacije se ne računaju — promene rasporeda da

CLS ignoriše transform animacije, ali ne i promene veličine ili položaja preko svojstava rasporeda. Baner koji posle učitavanja gura sadržaj nadole se računa; isti baner koji uklizava sa transform preko rezervisanog prostora — ne.

Poštujte smanjen pokret

Neki posetioci od operativnog sistema traže manje pokreta. Poštujte to — to je zahtev pristupačnosti, a ti posetioci dobijaju i lakšu stranicu:

css
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

U JavaScriptu, proverite matchMedia('(prefers-reduced-motion: reduce)') pre pokretanja petlji, paralakse i video zapisa koji se sami puštaju.

Postupak koji hvata nazadovanja

  1. Snimite Performance trag u Chrome DevTools uz 4× usporen CPU dok skrolujete celu stranicu. Tražite duge zadatke i ljubičaste trake rasporeda.
  2. Uključite Rendering → Paint flashing i Layout Shift Regions da vidite šta se ponovo iscrtava i pomera.
  3. Posle lansiranja, nedeljno proveravajte terenske podatke u PageSpeed Insights; laboratorijski rezultati su smernica, terenski podaci su ocena.

Kontrolna lista

  • Sadržaj naslovne sekcije vidljiv u početnom HTML-u, a ne iz nulte prozirnosti
  • LCP slika unapred učitana sa fetchpriority="high"
  • Teške biblioteke učitane po potrebi
  • Animiraju se samo transform i opacity
  • Čitanje rasporeda grupisano u requestAnimationFrame
  • Petlje, platna i video van ekrana pauzirani
  • Ograničen odnos piksela platna
  • Dimenzije na svim medijima; fontovi sa rezervnim fontovima usklađenih metrika
  • Poštuje se prefers-reduced-motion

Za stranu animacija, pogledajte GSAP u React-u i Next.js-u.

Bilten

Novi tekstovi na email

Kratak email kad objavim nešto novo. Bez spama, a pretplatu možete otkazati u bilo kom trenutku.

GSAP u React-u i Next.js-u: useGSAP, čišćenje i ScrollTrigger
GSAP u React-u i Next.js-u: useGSAP, čišćenje i ScrollTrigger
Sledeći tekst →