Scrollytelling

Scrollytelling ist eine beliebte Technik, um ansprechende und immersive Webinhalte zu erstellen. Dabei werden Elemente auf einer Seite animiert, während der Nutzer scrollt. So wird eine Geschichte erzählt oder der Nutzer durch eine Erzählung geführt. Mit scrollgesteuerten CSS-Animationen können Sie diese Effekte direkt in CSS erstellen, ohne JavaScript verwenden zu müssen. Die Animationen werden durch die Scrollposition gesteuert, nicht durch eine zeitbasierte Uhr. So sind sie immer mit dem Scrollen des Nutzers synchronisiert.

Implementierung

Für ein Scrollytelling-Erlebnis benötigen Sie zwei Arten von Elementen: eines, um die Scrollposition zu verfolgen, und ein anderes, das animiert werden soll.

Definieren Sie zuerst einen benannten view-timeline für die Elemente, die Sie erfassen möchten. Diese dienen als Treiber für Ihre Animationen.

#tracked {
  section:nth-child(1){ view-timeline: --tl-1 block; }
  section:nth-child(2){ view-timeline: --tl-2 block; }
  section:nth-child(3){ view-timeline: --tl-3 block; }
  section:nth-child(4){ view-timeline: --tl-4 block; }
  section:nth-child(5){ view-timeline: --tl-5 block; }
}

Wenden Sie als Nächstes Animationen auf die Elemente an, die Sie animieren möchten, und verknüpfen Sie sie mit den gerade erstellten Zeitachsen. Verwenden Sie dazu das Attribut animation-timeline.

#animated {
  section {
    animation: animate-in auto linear both, animate-out auto linear forwards;
    animation-range: entry 25% cover 50%, exit 50% exit 75%;
  }

  section:nth-child(1){ animation-timeline: --tl-1; }
  section:nth-child(2){ animation-timeline: --tl-2; }
  section:nth-child(3){ animation-timeline: --tl-3; }
  section:nth-child(4){ animation-timeline: --tl-4; }
  section:nth-child(5){ animation-timeline: --tl-5; }
}

Damit die animation-timeline auf die benannten Zeitachsen verweisen kann, müssen sie sich im selben Bereich befinden. Sie können das Attribut timeline-scope für ein gemeinsames übergeordnetes Element verwenden, um die Zeitachsen für alle Elemente verfügbar zu machen, die sie benötigen. Das :root-Element ist dafür oft eine gute Wahl.

html {
  timeline-scope: --tl-1, --tl-2, --tl-3, --tl-4, --tl-5;
}

Schließlich können Sie mit dem Attribut animation-range den genauen Bereich der Zeitachse angeben, in dem die Animation ausgeführt werden soll. So können Sie genau steuern, wann die Animationen ausgelöst werden und wie sie ablaufen.

#animated section {
  animation-range: entry 25% cover 50%, exit 50% exit 75%;
}

Beispielcode

html {
  timeline-scope: --tl-1, --tl-2, --tl-3, --tl-4, --tl-5;
}

#tracked {
  section:nth-child(1){ view-timeline: --tl-1 block; }
  section:nth-child(2){ view-timeline: --tl-2 block; }
  section:nth-child(3){ view-timeline: --tl-3 block; }
  section:nth-child(4){ view-timeline: --tl-4 block; }
  section:nth-child(5){ view-timeline: --tl-5 block; }
}

@keyframes animate-in {
  from { scale: 0.5; opacity: 0; transform: rotateY(-180deg); }
  to { transform: rotateY(0deg); }
}
@keyframes animate-out {
  to { translate: 100% 0; opacity: 0; }
}

#animated {
  section {
    animation: animate-in auto linear both, animate-out auto linear forwards;
    animation-range: entry 25% cover 50%, exit 50% exit 75%;
    backface-visibility: hidden;
  }

  section:nth-child(1){ animation-timeline: --tl-1; }
  section:nth-child(2){ animation-timeline: --tl-2; }
  section:nth-child(3){ animation-timeline: --tl-3; }
  section:nth-child(4){ animation-timeline: --tl-4; }
  section:nth-child(5){ animation-timeline: --tl-5; }
}

/* MANDATORY Copy-Paste Safety: Disable continuous storytelling motion for sensitive users */
@media (prefers-reduced-motion: reduce) {
  #animated section {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
}

Best Practices

Wenn Sie scrollbasierte Animationen verwenden, sollten Sie einige Best Practices beachten, um eine reibungslose und barrierefreie Nutzung zu ermöglichen:

  • DO include feature detection: Not all browsers support scroll-driven animations. Verwenden Sie @supports ((animation-timeline: scroll()) and (animation-range: 0% 100%)), um die Unterstützung zu prüfen, und stellen Sie einen Fallback für Browser bereit, die die Funktion nicht unterstützen.
    • Die (animation-range: 0% 100%)-Prüfung MUSS hier enthalten sein, um Browser mit nur teilweiser Unterstützung herauszufiltern.
    • Verwenden Sie NICHT das Paket scroll-timeline-polyfill für die Fallback-Strategie, da es nicht vollständig ist und viele bekannte Probleme aufweist.
    • Wenn die Animation nur dekorativ ist, entscheide dich für die progressive Verbesserung und stelle KEIN Fallback bereit.
  • DO respect user preferences: Some users prefer to have less motion on the web. Verwenden Sie die Medienabfrage prefers-reduced-motion, um Ihre Animationen für diese Nutzer zu deaktivieren oder zu reduzieren.
  • DO try to animate only performant CSS properties: For the smoothest animations, stick to animating properties that can be handled by the browser's compositor thread, such as transform and opacity. Das Animieren anderer Attribute wie width oder height kann zu Leistungsproblemen führen.
  • RICHTIG: Verwenden Sie die richtige Deklarationsreihenfolge. Wenn Sie die Kurzschreibweise animation verwenden, deklarieren Sie animation-timeline und animation-range danach, um zu verhindern, dass die Kurzschreibweise die Zeitachse zurücksetzt.

Wenn Sie die Eigenschaft view-timeline verwenden, um eine scrollbasierte Animation zu erstellen:

  • Verwenden Sie für den Namen ein CSS-<dashed-ident>. (EMPFOHLEN)
  • OPTIONAL: Achte darauf, die zu verfolgende Achse explizit anzugeben. Wenn du nicht die Standardachse block verwendest (z. B. bei einem horizontalen Scroller), gib explizit an, welche Achse mit view-timeline-axis verfolgt werden soll.
  • DO: Achten Sie darauf, dass der Suchbereich funktioniert. Wenn das Element, das die view-timeline deklariert, kein flacher Baum-Ancestor des animierten Elements ist, erhöhen Sie die Sichtbarkeit des Namens der view-timeline, indem Sie timeline-scope für einen gemeinsamen Ancestor verwenden.

Fallback-Strategien

Für Browser, die keine scrollgesteuerten Animationen unterstützen, können Sie einen Fallback verwenden, um die visuellen Effekte nachzubilden. Die Fallbacks werden in der Regel entweder mit einem Scroll-Listener (für ScrollTimeline-Effekte) oder der IntersectionObserver API (für ViewTimeline-Effekte) erstellt.

Verwenden Sie in Browsern mit integrierter Unterstützung für scrollgesteuerte Animationen IMMER die native CSS-Implementierung, da diese leistungsfähiger ist.

Beachten Sie, dass nicht jeder Effekt mit dem Fallback-Ansatz nachgebildet werden kann.

Speziell für diesen Anwendungsfall wird im folgenden Script der Fallback für Browser angewendet, die keine scrollgesteuerten Animationen unterstützen. Es wird ein IntersectionObserver verwendet, um die Sichtbarkeit jedes #tracked section-Elements zu verfolgen und die transform-Eigenschaft des entsprechenden #animated section entsprechend zu aktualisieren.

const animatedSections = document.querySelectorAll('#animated section');

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    const sectionIndex = Array.from(document.querySelectorAll('#tracked section')).indexOf(entry.target);
    if (sectionIndex !== -1) {
      const animatedSection = animatedSections[sectionIndex];
      const ratio = entry.intersectionRatio;

      // Animate-in
      animatedSection.style.opacity = ratio;
      animatedSection.style.transform = `scale(${0.5 + ratio * 0.5}) rotateY(${-180 + ratio * 180}deg)`;

      // Animate-out
      if (ratio < 0.5) {
        animatedSection.style.translate = `${(0.5 - ratio) * 2 * 100}% 0`;
      } else {
        animatedSection.style.translate = '0 0';
      }
    }
  });
}, { threshold: Array.from({length: 101}, (_, i) => i / 100) });

document.querySelectorAll('#tracked section').forEach(section => {
  observer.observe(section);
});

Und der zugehörige CSS-Code:

#animated section {
  opacity: 0;
  transform: scale(0.5)  rotateY(-180deg);
  backface-visibility: hidden;
}

/* MANDATORY Copy-Paste Safety: Ensure content remains fully visible and legible for assistive technologies or users with motion sensitivities */
@media (prefers-reduced-motion: reduce) {
  #animated section {
    opacity: 1 !important;
    transform: none !important;
    translate: 0 0 !important;
  }
}

Dieses Fallback bietet eine genauere, scrollbasierte Animation für Browser, die die native CSS-Funktion nicht unterstützen, und sorgt so für eine einheitlichere Darstellung für alle Nutzer. Durch die Verwendung einer Reihe von Schwellenwerten für IntersectionObserver können wir die Scrollposition genauer verfolgen und eine flüssigere Animation erstellen.