Hoe scroll-getriggerde animaties CLS veroorzaken

Je hide-on-scroll header laat je CLS misschien ongemerkt falen. Dit is waarom, en hoe je het oplost.

Arjen Karel Core Web Vitals Consultant
Arjen Karel - linkedin
Last update: 2026-03-26

Wanneer ik gevraagd word om een audit van de Cumulative Layout Shift uit te voeren, begin ik natuurlijk met shifts tijdens de laadfase. Ik controleer op afbeeldingen zonder dimensies, geïnjecteerde advertenties, lettertypewisselingen (FOUT).

Maar niet alle CLS gebeurt tijdens het laden van de pagina. Soms wordt het getriggerd door interacties zoals scrollen. Deze zijn moeilijk te vangen omdat Lighthouse niet scrolt en je precies moet weten waar je moet zoeken.

Laatst beoordeeld door Arjen Karel in maart 2026

In een recente audit heb ik CoreDash RUM data toegevoegd en zag ik een verdacht patroon. Een #header element leek veel meer layout shift te veroorzaken dan normaal.

Het blijkt dat wat we hier zien een fixed header is die verbergt wanneer de gebruiker naar beneden scrolt en weer verschijnt wanneer ze naar boven scrollen. Het is een patroon dat ik heel vaak zie en als het niet goed wordt gedaan, zal het een layout shift veroorzaken bij elke verandering van scrollrichting.

Waarom scroll-getriggerde shifts anders zijn

Normaal gesproken gebeurt CLS niet na een interactie. CLS meet onverwachte layout shifts en na een interactie zou je verwachten dat de layout verandert. Daarom heeft de CLS metric een automatische grace period van 500ms. Layout shifts die plaatsvinden binnen 500 milliseconden na een gebruikersinteractie worden uitgesloten van de CLS score. De browser regelt dit door een hadRecentInput flag op deze shifts in te stellen en te controleren.

Het probleem is dat dit alleen geldt voor discrete events zoals klikken, tikken en toetsaanslagen. Volgens de Layout Instability specificatie is scroll nadrukkelijk geen uitsluitende input. Sleeps of pinch-zoom gebaren ook niet. Alleen discrete events krijgen de 500ms grace period.

Dit betekent dat elke layout shift die wordt getriggerd door een scroll event meetelt voor je CLS score.

Volgens de 2025 Web Almanac gebruikt 40% van de mobiele pagina's nog steeds non-composited animaties. Dit zijn animaties op properties zoals top, height of margin die bij elke frame een layout herberekening triggeren. Als een van deze tijdens een scroll afgaat, telt elke afzonderlijke frame mee voor je CLS.

Het hide-on-scroll header probleem

Laten we beginnen met het probleem dat ik vandaag vond, het 'hide-on-scroll header probleem'. Een fixed header gebruikt JavaScript om de zichtbaarheid tijdens het scrollen te wisselen. De implementatie ziet er zo uit:

/* CSS */
#header {
  position: fixed;
  top: 0;
  transition: top 0.3s ease;
}

/* JavaScript */
window.addEventListener('scroll', () => {
  if (scrollingDown) {
    header.style.top = '-80px';  // hide
  } else {
    header.style.top = '0px';    // show
  }
});

De layout shift wordt veroorzaakt door de combinatie van transition: top 0.3s ease en het veranderen van header.style.top in JavaScript.

Dit is waarom: top is een layout property. Wanneer de browser een verandering aan top verwerkt, doorloopt deze de volledige rendering pipeline: Style, Layout, Paint, Composite. Bij elke animation frame tijdens die transitie herberekent de browser de layout. Elke herberekening produceert een nieuwe layout shift entry.

Bij elke verandering van scrollrichting blijft de CLS teller oplopen zolang de gebruiker interacteert met de pagina.

Ik heb een demo gebouwd die exact dit gedrag reproduceert van een live site die ik aan het auditen was en heb een video opgenomen. De totale CLS klom voorbij 0.1 na wat scrollen. Toegegeven: dit niveau van scrollen gebeurt in het echte leven niet vaak, maar de mechanica veranderen niet. En ik heb gezien dat deze layout shift sites over het randje duwt waardoor hun totale CLS faalt.

Welke CSS properties kunnen layout shifts veroorzaken bij het animeren?

Om CLS te veroorzaken moet een animatie Layout triggeren. De rendering pipeline van de browser heeft na de style berekening drie hoofdfasen: Layout, Paint en Composite. Properties die alleen de Composite fase beïnvloeden, veroorzaken nooit layout shifts. Voor meer informatie over hoe CSS transities layout shifts veroorzaken, zie het bijbehorende artikel.

Properties die layout triggeren (en CLS veroorzaken bij het animeren):

top, left, right, bottom, width, height, margin, padding, border-width, font-size

Al deze veranderen de geometrie of positie van een element. De browser moet de positie van het element (en mogelijk ook van andere elementen) herberekenen.

Properties die layout volledig overslaan (en veilig zijn voor animatie):

transform en opacity draaien op de compositor thread. Ze omzeilen zowel layout als paint. De browser verwerkt ze op de GPU (dit is waarom deze animaties werken zelfs als de main thread bezig is). Omdat ze nooit een layout herberekening triggeren, produceren ze nooit layout shift entries.

Vervang layout properties door transforms

De fix voor de hide-on-scroll header is simpel. Vervang de top animatie door transform: translateY().

/* CSS - BEFORE (causes CLS) */
#header {
  position: fixed;
  top: 0;
  transition: top 0.3s ease;
}

/* CSS - AFTER (no CLS) */
#header {
  position: fixed;
  top: 0;
  transition: transform 0.3s ease;
}

/* JavaScript - BEFORE */
header.style.top = '-80px';

/* JavaScript - AFTER */
header.style.transform = 'translateY(-80px)';

Dit geeft hetzelfde resultaat: de header schuift omhoog en verdwijnt. Maar transform: translateY() draait volledig op de compositor en veroorzaakt nooit een layout shift.

Hetzelfde principe is van toepassing op elke scroll-getriggerde animatie:

  • Schuivende panelen: gebruik transform: translateX() in plaats van left of right
  • Uitklappende elementen: gebruik transform: scaleY() in plaats van height
  • Vervagende elementen: gebruik opacity in plaats van visibility gecombineerd met hoogteveranderingen

Andere scroll-getriggerde patronen die CLS veroorzaken

De hide-on-scroll header is iets wat ik vrij vaak zie, maar er zijn er meer. Elke JavaScript die een layout property verandert als reactie op scroll events, zal een layout shift produceren. Bijvoorbeeld:

Parallax effecten die top of margin-top gebruiken. Vervang door transform: translateY() of gebruik de CSS perspective en translateZ aanpak voor pure-CSS parallax.

Sticky navigatie die in hoogte verandert tijdens het scrollen. Een navigatiebalk die krimpt van 80px naar 50px wanneer de gebruiker voorbij een drempelwaarde scrolt. Als de hoogteverandering wordt geanimeerd met transition: height, vervang het dan door transform: scaleY().

Aan scrollen gekoppelde voortgangsbalken die width gebruiken. Een leesvoortgangsindicator die van links naar rechts groeit door width te animeren. Gebruik in plaats daarvan transform: scaleX() met transform-origin: left.

Modern alternatief: CSS Scroll-Driven Animaties. De animation-timeline: scroll() API laat je animaties sturen op basis van scrollvoortgang zonder enige JavaScript. Omdat deze animaties standaard transform en opacity gebruiken en op de compositor draaien, veroorzaken ze nooit layout shifts. Browserondersteuning omvat Chrome 115+ en Edge 115+, met Safari nog in ontwikkeling. Voor aan scrollen gekoppelde effecten zoals parallax, voortgangsbalken en reveal animaties, is dit de schoonste oplossing. Als je JavaScript scrollen volledig wilt dumpen, is de scroll-driven animaties API de weg vooruit.

Waarom Lighthouse dit niet opvangt

Lighthouse voert een gesimuleerde page load uit. Het scrolt niet over de pagina. Het interacteert niet met de pagina na het laden. Dit betekent dat scroll-getriggerde layout shifts volledig onzichtbaar zijn in lab testing.

De enige manier om deze shifts op te vangen is met Real User Monitoring. Field data legt de volledige levenscyclus van de pagina vast, inclusief elke layout shift die gebeurt terwijl de gebruiker scrolt, klikt en navigeert. Als je CrUX CLS score slechter is dan je Lighthouse CLS score, zijn scroll-getriggerde animaties een van de eerste dingen om te onderzoeken. CoreDash trackt CLS van echte bezoekers, inclusief elke shift veroorzaakt door scrollen, zodat je precies kunt zien welke elementen verantwoordelijk zijn.

Hoe je scroll-getriggerde layout shifts vindt

Open Chrome DevTools. Ga naar het Performance paneel. Start de opname (het cirkel icoon) en scrol de pagina dan een paar keer op en neer. Stop de opname. Zoek naar paarse diamanten in de Layout Shifts track. Als je een cluster van shifts ziet dat overeenkomt met je scrollactiviteit, heb je een scroll-getriggerd CLS probleem. Je kunt ook de Core Web Vitals Visualizer Chrome extensie gebruiken om CLS in real-time te zien terwijl je scrolt.

De vuistregel

Als het beweegt tijdens het scrollen, animeer het dan met transform. Als het vervaagt tijdens het scrollen, animeer het dan met opacity. Deze properties zijn compositor-only.

Voor JavaScript: check je scroll event handlers, IntersectionObserver callbacks, en alle JavaScript die de styling van elementen verandert als reactie op de scrollpositie. Als het top, left, margin, width, height of padding gebruikt, vervang het dan door de equivalente transform.

About the author

Arjen Karel is a web performance consultant and the creator of CoreDash, a Real User Monitoring platform that tracks Core Web Vitals data across hundreds of sites. He also built the Core Web Vitals Visualizer Chrome extension. He has helped clients achieve passing Core Web Vitals scores on over 925,000 mobile URLs.

Find out what is actually slow.

I map your critical rendering path using real field data. You get a clear answer on what blocks LCP, what causes INP spikes, and where layout shifts originate.

Book a Deep Dive
Hoe scroll-getriggerde animaties CLS veroorzakenCore Web Vitals Hoe scroll-getriggerde animaties CLS veroorzaken