aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Frontend & UI

Scroll-animatie: Performante Effecten met Intersection Observer

Een goede scroll-animatie laat elementen precies op het juiste moment verschijnen terwijl je naar beneden scrollt — niet te vroeg, niet te laat. Jarenlang bouwden we dit door naar het scroll-event te luisteren en elk frame met de hand te berekenen. De browser kan dit nu voor ons doen zonder de hoofdthread te belasten: IntersectionObserver. In deze gids loop ik stap voor stap door hoe je efficiënt detecteert wanneer een element in de viewport komt, hoe je dat combineert met CSS voor vloeiende overgangen, en hoe je de gebruikelijke valkuilen rond performance en toegankelijkheid vermijdt.

Waarom Intersection Observer in plaats van het scroll-event?

De klassieke aanpak gebruikte window.addEventListener('scroll', ...) en riep getBoundingClientRect() aan voor elk element bij elke scrollstap. Twee problemen: het scroll-event vuurt tientallen keren per seconde, en getBoundingClientRect() dwingt de browser de layout opnieuw te berekenen (reflow). Samen verstoppen ze de hoofdthread en gaat het scrollen haperen.

Intersection Observer werkt andersom: je vertelt de browser "laat me weten wanneer dit element in beeld komt", en de browser houdt dat intern bij, asynchroon en geoptimaliseerd. De callback draait alleen wanneer de intersectiestatus verandert, niet bij elk scrollframe. Het resultaat: minder code, minder reflow, een soepelere pagina.

Je eerste observer opzetten

Basisgebruik bestaat uit drie delen: de doelelementen om te observeren, een callback die bij intersectie draait, en opties die het gedrag afstemmen.

const observer = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      entry.target.classList.add('is-visible');
      // stop met observeren zodra het verschenen is
      observer.unobserve(entry.target);
    }
  });
}, {
  threshold: 0.2,        // vuur wanneer 20% van het element zichtbaar is
  rootMargin: '0px 0px -10% 0px'
});

document.querySelectorAll('.reveal').forEach((el) => {
  observer.observe(el);
});

De belangrijkste truc hier: JavaScript voegt alleen een CSS-klasse toe (is-visible). CSS doet de eigenlijke animatie. Die scheiding is cruciaal, zowel voor performance als voor onderhoud.

De animatie aan CSS overlaten

We definiëren de begin- en eindstaat van het element in CSS en verzachten de overgang ertussen met transition. Het gebruik van transform en opacity is belangrijk: deze twee eigenschappen worden op de GPU verwerkt en triggeren geen layout-herberekening.

.reveal {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.6s ease, transform 0.6s ease;
}

.reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
}

Met dezelfde structuur kun je varianten maken zoals inschuiven van links, schalen of een lichte rotatie via verschillende klassen. Nog een voordeel van animatie in CSS houden: zelfs als JavaScript niet draait, mag de content niet verborgen blijven op opacity: 0 — dat borgen we in de toegankelijkheidssectie hieronder.

threshold en rootMargin correct afstemmen

Deze twee opties bepalen volledig hoe een scroll-animatie aanvoelt:

  • threshold: een verhouding tussen 0 en 1. 0 vuurt zodra één pixel zichtbaar is, 1 wanneer het hele element zichtbaar is. Je kunt ook een array doorgeven ([0, 0.25, 0.5, 1]) en de callback bij elke drempel ontvangen.
  • rootMargin: vergroot of verkleint het rootvlak; het gebruikt de CSS margin-syntax. Een negatieve onderwaarde zoals -10% stelt de trigger uit tot het element wat verder in het scherm staat, zodat de animatie "precies op tijd" voelt.
  • root: standaard de viewport (null). Observeer je binnen een scrollbare container, geef dan dat element op als root.

Getrapt (stagger) verschijneffect

Kaarten die na elkaar met een lichte vertraging verschijnen, zijn geliefd in moderne interfaces. Je doet dit netjes door elk element een vertraging te geven via een CSS-variabele:

const items = document.querySelectorAll('.reveal');
const io = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    if (!entry.isIntersecting) return;
    const i = Number(entry.target.dataset.index || 0);
    entry.target.style.setProperty('--delay', `${i * 80}ms`);
    entry.target.classList.add('is-visible');
    io.unobserve(entry.target);
  });
}, { threshold: 0.15 });

items.forEach((el, i) => {
  el.dataset.index = i;
  io.observe(el);
});

Aan de CSS-kant koppel je die vertraging aan de overgang: transition-delay: var(--delay, 0ms);. Vier kaarten verschijnen dan op volgorde, in een vloeiende golf.

Valkuilen rond performance en toegankelijkheid

Enkele details maken het project professioneel:

  • Beheer het opnieuw triggeren: zodra een element is verschenen, stop met observeren via unobserve(); anders reset de animatie wanneer de gebruiker terug omhoog scrollt. Wil je wel dat hij opnieuw afspeelt, verwijder dan de klasse is-visible bij !entry.isIntersecting.
  • Voorkeur voor minder beweging: sommige gebruikers hebben last van beweging. Gebruik @media (prefers-reduced-motion: reduce) om overgangen uit te zetten en content direct te tonen.
  • Houd content niet verborgen als JavaScript uit staat: pas de initiële verborgen staat alleen toe wanneer JS actief is. Een praktische aanpak: voeg document.documentElement.classList.add('js') toe aan het <html>-element en conditioneer de CSS als .js .reveal { opacity: 0; }.
@media (prefers-reduced-motion: reduce) {
  .reveal {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

Veelgestelde vragen

Hoe zit het met browserondersteuning voor Intersection Observer?

Het wordt al jaren ondersteund in alle huidige browsers (Chrome, Firefox, Safari, Edge); geen aparte bibliotheek nodig. Tenzij je heel oude browsers target, heb je ook geen polyfill nodig.

Is naar scroll luisteren met requestAnimationFrame beter?

Voor continue effecten die gedurende het scrollen veranderen, zoals parallax, is requestAnimationFrame geschikt. Maar om te weten of een element zichtbaar is, is Intersection Observer altijd de efficiëntere en eenvoudigere oplossing.

Moet de animatie bij elke scroll opnieuw afspelen?

Meestal niet. Laat content één keer verschijnen en zo blijven; voortdurend herhalende verschijnanimaties leiden af en belasten de performance. In zeldzame gevallen kun je herhalen, maar unobserve() is de aanbevolen standaard.

Wil je vloeiende, performante scroll-animaties op je site? Voor toegankelijke, hapervrije interface-effecten afgesteld met de juiste drempels, neem contact met me op.

Bu kategorideki tüm yazılar →

Devamı için