Voeg effecten voor binnenkomst en vertrek toe aan elementen wanneer ze de scrollport binnenkomen of verlaten.

In- en uitgangeffecten zijn animaties die worden geactiveerd wanneer een element de viewport binnenkomt of verlaat. Dit kan worden gebruikt om aantrekkelijke en dynamische gebruikerservaringen te maken. U kunt bijvoorbeeld een ingangseffect gebruiken om een element in te faden terwijl het in beeld scrollt, of een uitgangseffect om het te verkleinen terwijl het uit beeld scrollt.

Hoe wordt dit geïmplementeerd

Als u effecten voor binnenkomst en vertrek aan een element wilt toevoegen, moet u een aantal CSS-eigenschappen combineren. Hier volgt een stapsgewijze handleiding:

  1. Maak afzonderlijke @keyframes voor de animaties bij openen en sluiten. De animatie voor binnenkomst wordt toegepast als het element het kijkvenster binnenkomt en de animatie voor verlaten wordt toegepast als het element het kijkvenster verlaat.

    @keyframes slide-in {
      from { transform: translateX(-100%); }
    }
    @keyframes slide-out {
      to { transform: translateX(100%); }
    }
    
  2. Koppel de begin- en eindkeyframes aan het element. Je kunt dit doen door meerdere animaties te definiëren in de eigenschap animation.

    • Geef de animatie bij openen een animation-fill-mode van backwards, zodat de beginstatus wordt toegepast voordat de animatie begint.
    • Geef de animatie voor verbergen een animation-fill-mode van forwards, zodat de uiteindelijke status behouden blijft nadat de animatie is voltooid.
    .animated-element {
      animation:
        slide-in 1s linear backwards,
        slide-out 1s linear forwards;
    }
    
  3. Maak een View Timeline en koppel deze aan de animaties. Een weergavetijdlijn is een type tijdlijn dat is gekoppeld aan de zichtbaarheid van een element in de viewport. U kunt er een maken met de functie view() en deze daarna toepassen op uw animaties met de eigenschap animation-timeline.

    .animated-element {
      animation-timeline: view();
    }
    

    Standaard volgt view() het element op de block-as. Als je dit op de inline-as wilt bijhouden, kun je view(inline) gebruiken.

  4. Beperk de animaties tot de bereiken entry en exit. Met de property animation-range kunt u aangeven op welk deel van de tijdlijn een animatie moet worden uitgevoerd.

    • Het bereik entry omvat de tijd vanaf het moment dat het element voor het eerst in de viewport komt tot het moment dat het volledig zichtbaar is.
    • Het bereik exit omvat de tijd vanaf het moment dat het element het kijkvenster begint te verlaten totdat het helemaal verborgen is.
    .animated-element {
      animation-range: entry, exit;
    }
    

Voorbeeldcode

Deze code animeert de directe onderliggende elementen van de scroller bij scrollen met een anonieme weergavetijdlijn:

@media (prefers-reduced-motion: no-preference) {
  @supports ((animation-timeline: view()) and (animation-range: entry)) {
    @keyframes grow {
      from {
        scale: 0.5;
      }
    }
    @keyframes shrink {
      to {
        scale: 0.5;
      }
    }

    .scroller > * {
      animation:
        grow auto linear backwards,
        shrink auto linear forwards;
      animation-timeline: view(inline);
      animation-range: entry, exit;
    }
  }
}

Als de elementen de scrollport binnenkomen, wordt de animatie grow afgespeeld. Als ze de scrollport verlaten, wordt de animatie shrink afgespeeld.

De volgende code heeft hetzelfde visuele resultaat, maar animeert de directe onderliggende elementen van de scroller bij scrollen met een benoemde weergavetijdlijn:

@media (prefers-reduced-motion: no-preference) {
  @supports ((animation-timeline: view()) and (animation-range: entry)) {
    @keyframes grow {
      from {
        scale: 0.5;
      }
    }
    @keyframes shrink {
      to {
        scale: 0.5;
      }
    }

    .scroller > * {
      view-timeline: --tl inline;
      animation:
        grow auto linear backwards,
        shrink auto linear forwards;
      animation-timeline: --tl;
      animation-range: entry, exit;
    }
  }
}

Praktische tips

Als je scrollgestuurde animaties gebruikt, is het belangrijk dat je een aantal best practices volgt om een soepele en toegankelijke gebruikerservaring te bieden:

  • WEL functiedetectie opnemen: Niet alle browsers ondersteunen scrollgestuurde animaties. Gebruik @supports ((animation-timeline: view()) and (animation-range: entry)) om te controleren op ondersteuning en een fallback te bieden voor browsers die dit niet ondersteunen.
    • De (animation-range: entry)-controle MOET hier worden opgenomen om browsers met slechts gedeeltelijke ondersteuning weg te filteren.
    • Gebruik het scroll-timeline-polyfill-pakket NIET voor de fallback-strategie, omdat het niet alle functies bevat en veel bekende problemen heeft.
    • Als de animatie alleen als decoratief wordt beschouwd, kies je voor progressive enhancement en bied je GEEN fallback.
  • WEL: Respecteer gebruikersvoorkeuren. Sommige gebruikers geven de voorkeur aan minder beweging op het web. Gebruik de mediaquery prefers-reduced-motion om je animaties uit te zetten of te verminderen voor deze gebruikers.
  • WEL proberen alleen animaties te maken voor presterende CSS-property's: Voor de soepelste animaties maak je alleen animaties voor property's die kunnen worden verwerkt door de compositor-thread van de browser, zoals transform en opacity. Als je andere eigenschappen animeert, zoals width of height, kan dit leiden tot prestatieproblemen.
  • Gebruik WEL de juiste declaratievolgorde: Als je de verkorte eigenschap animation gebruikt, declareer je animation-timeline erna om te voorkomen dat de verkorte eigenschap de tijdlijn reset.

Als je de functie view() gebruikt om een scrollgestuurde animatie te maken, geldt het volgende:

  • OPTIONEEL Wees expliciet over de as die je wilt bijhouden: Als je niet de standaardas block target (zoals in een horizontale scroller), wees dan expliciet over welke as je wilt bijhouden met view(block) of view(inline).
  • Als de animatie niet wordt toegepast op het gevolgde onderwerp zelf, gebruik je een tijdlijn met een benoemde weergave.

Als je de eigenschap view-timeline gebruikt om een scrollgestuurde animatie te maken:

  • WEL een CSS <dashed-ident> gebruiken voor de naam.
  • OPTIONEEL Wees expliciet over de as die moet worden bijgehouden: Als je niet de standaardas block target (zoals in een horizontale scroller), wees dan expliciet over welke as moet worden bijgehouden met view-timeline-axis.
  • Zorg dat het bereik van de zoekopdracht werkt: Als het element dat de view-timeline declareert, geen voorouder van de platte structuur van het geanimeerde element is, verhoog je de zichtbaarheid van de naam van de view-timeline door timeline-scope te gebruiken voor een gedeelde voorouder.

Geef de voorkeur aan een benoemde view-timeline als meerdere elementen of onderliggende elementen van het gevolgde onderwerp moeten worden geanimeerd.

Browserondersteuning en fallback-strategieën

Daarom is meestal een reservestrategie vereist.

Voor browsers die geen scrollgestuurde animaties ondersteunen, kun je een fallback gebruiken om de visuele effecten opnieuw te maken. De fallbacks worden meestal gemaakt met een scroll-listener (voor ScrollTimeline-effecten) of de IntersectionObserver API (voor ViewTimeline-effecten).

In browsers met ingebouwde ondersteuning voor scrollgestuurde animaties gebruik je ALTIJD de native CSS-implementatie, omdat die beter presteert.

Niet elk effect kan opnieuw worden gemaakt met de reserveaanpak.

Specifiek voor deze use case past het volgende script de fallback toe voor browsers die geen scrollgestuurde animaties ondersteunen. De functie gebruikt een IntersectionObserver om de zichtbaarheid van het element .wrapper bij te houden en updatet de property transform van de lagen op basis van de scrollpositie.

<script>
  if (!CSS.supports('(animation-timeline: view()) and (animation-range: entry)')) {
    const observer = new IntersectionObserver(
      (entries) => {
        for (const entry of entries) {
          // This matches the effect as defined in the CSS example above.
          // Customize this further if needed.
          entry.target.style.scale = 0.5 + entry.intersectionRatio * 0.5;
        }
      },
      {
        threshold: Array.from({ length: 101 }, (_, i) => i / 100),
      }
    );

    document.querySelectorAll('.scroller > *').forEach((el) => {
      observer.observe(el);
    });
  }
</script>