Kop verkleinen bij scrollen

Een krimpende koptekst bij scrollen is een veelvoorkomend UI-patroon waarbij een vast kopelement bovenaan de pagina soepel overgaat naar een kleiner formaat als de gebruiker omlaag scrollt. Dit effect wordt vaak gebruikt om de schermruimte voor de hoofdcontent te maximaliseren, terwijl essentiële navigatie- of brandingelementen toegankelijk blijven. Met CSS-scrollgestuurde animaties kan dit effect op een declaratieve en presterende manier worden bereikt door een animatie te koppelen aan de scrollpositie van het document.

Hoe wordt dit geïmplementeerd

Zo maak je een krimpende koptekst bij scrollen:

  1. Maak een vaste koptekst: Begin met een koptekstelement dat bovenaan de pagina is vastgezet en een vooraf gedefinieerde hoogte heeft.

    <header>HEADER</header>
    
    header {
      position: fixed;
      height: 200px;
      top: 0;
      left: 0;
      right: 0;
    }
    
  2. Definieer de krimp-animatie: Maak een CSS-animatie die de hoogte van de kop wijzigt.

    @keyframes shrink {
      to {
        height: 50px;
      }
    }
    
  3. De animatie en scrolltijdlijn toepassen: Koppel de animatie aan de koptekst en gebruik de functie scroll() om deze te koppelen aan de scrollpositie van het document.

    header {
      animation: shrink auto linear both;
      animation-timeline: scroll(block root);
    }
    
  4. Stel de animation-range in: Gebruik de property animation-range om de scrollafstand aan te geven waarover de animatie moet plaatsvinden. Als u de kop bijvoorbeeld wilt verkleinen over de eerste 150 pixels van het scrollen, gebruikt u animation-range: 0px 150px;.

    header {
      animation-range: 0px 150px;
    }
    

Tip: Als je wilt voorkomen dat de content na de koptekst wordt verborgen, voeg je een padding-top toe aan de body (of de container voor de hoofdcontent) die gelijk is aan de oorspronkelijke hoogte van de koptekst.

Tip: Als je wilt dat de content van de pagina synchroon met de krimpende koptekst scrollt, stel je de animation-range-end in op het verschil tussen de begin- en eindgrootte. Zo wordt de animatie precies afgerond wanneer de koptekst de uiteindelijke grootte bereikt. In deze demo wordt de koptekst verkleind van 200px naar 50px, dus de animation-range-end is ingesteld op 150px.

Voorbeeldcode

@keyframes shrink {
  to {
    height: 50px;
  }
}

header {
  animation: shrink auto linear both;
  animation-timeline: scroll(block root);
  animation-range: 0px 150px;
}

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: scroll()) and (animation-range: 0% 100%)) om te controleren op ondersteuning en een fallback te bieden voor browsers die dit niet ondersteunen.
    • De (animation-range: 0% 100%)-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.
  • WEL de juiste declaratievolgorde gebruiken: Als je de verkorte eigenschap animation gebruikt, declareer je animation-timeline en animation-range erna om te voorkomen dat de verkorte eigenschap de tijdlijn reset.

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

  • OPTIONEEL Wees expliciet over de scroller: Als je niet de dichtstbijzijnde bovenliggende scroller target, wees dan expliciet over welke scroller je wilt gebruiken met scroll(root) of scroll(self).
    • Als root, nearest of self niet voldoende zijn, gebruik je een benoemde scroll-timeline.
  • 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 scroll(block) of scroll(inline).

Voor deze specifieke toepassing geldt het volgende:

  • Het element dat je animeert, mag NIET position: static of position: relative zijn als je percentages gebruikt in de animation-range.
    • Dit komt omdat die elementen worden beschouwd als 'in-flow'. Als je die elementen verkleint terwijl je scrollt, wordt de totale scrollafstand kleiner. Dit heeft invloed op de berekende waarde van bijvoorbeeld 10% in de scroll.

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 scroll-listener om de scrollpositie van het document over een afstand van 150px bij te houden en de hoogte van de koptekst dienovereenkomstig te updaten.

// Fallback for browsers that don't support scroll-driven animations
if (!CSS.supports('(animation-timeline: scroll()) and (animation-range: 0% 100%)')) {
  const header = document.querySelector('header');

  const initialHeight = 200;
  const finalHeight = 50;
  const scrollDistance = 150;

  window.addEventListener('scroll', () => {
    const scrollY = window.scrollY;
    const scrollPercent = Math.min(1, scrollY / scrollDistance);
    const newHeight = initialHeight - (initialHeight - finalHeight) * scrollPercent;

    header.style.height = `${newHeight}px`;
  });
}