Was macht Scroll-Animation (GSAP + ScrollTrigger + Lenis)?
Wenn die offiziellen GreenSock-Skills (gsap-core, gsap-scrolltrigger, gsap-react, gsap-plugins) installiert sind, verwende diese für API-Details; dieser Skill behandelt Projekteinrichtung, Integration und die Fehler, die echte Websites zum Absturz bringen.
1. Installation
npm i gsap lenis
npm i @gsap/react # Nur React / Next.js
Alle GSAP-Plugins (ScrollTrigger, SplitText, Flip, ScrollSmoother, MorphSVG, DrawSVG…) sind im öffentlichen gsap-Paket enthalten und für die kommerzielle Nutzung kostenlos. Importiere aus gsap/<PluginName> und rufe gsap.registerPlugin(...) einmal auf.
2. Ein einziges Setup-Modul
Erstelle lib/motion/setup.(js|ts) (oder animations/), das einmal auf dem Client importiert wird:
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { SplitText } from "gsap/SplitText";
import Lenis from "lenis";
import "lenis/dist/lenis.css";
gsap.registerPlugin(ScrollTrigger, SplitText);
export function initSmoothScroll() {
if (matchMedia("(prefers-reduced-motion: reduce)").matches) return null;
const lenis = new Lenis();
lenis.on("scroll", ScrollTrigger.update);
const tick = (time) => lenis.raf(time * 1000); // GSAP-Zeit ist in Sekunden, Lenis erwartet ms
gsap.ticker.add(tick);
gsap.ticker.lagSmoothing(0);
return () => { gsap.ticker.remove(tick); lenis.destroy(); };
}
Verwende entweder Lenis oder GSAP ScrollSmoother, nicht beide.
3. Framework-Integration
React / Next.js (App Router) – Dateien, die GSAP verwenden, benötigen "use client". Verwende immer useGSAP (automatische Bereinigung, kein doppelter Durchlauf im Strict Mode):
"use client";
import { useRef } from "react";
import gsap from "gsap";
import { useGSAP } from "@gsap/react";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(useGSAP, ScrollTrigger);
export function Features() {
const root = useRef<HTMLElement>(null);
useGSAP(() => {
const mm = gsap.matchMedia();
mm.add("(prefers-reduced-motion: no-preference)", () => {
gsap.from(".feature", {
autoAlpha: 0, y: 32, stagger: 0.08, ease: "power3.out",
scrollTrigger: { trigger: root.current, start: "top 75%" },
});
});
}, { scope: root }); // Selektoren sind auf diese Komponente beschränkt
return <section ref={root}>…</section>;
}
- Event-Handler, die Animationen erzeugen: mit
contextSafeaususeGSAPumschließen. - Smooth-Scroll-Provider: eine Client-Komponente im Root-Layout, die
initSmoothScroll()in einem Effekt aufruft (oderReactLenisvonlenis/reactmitautoRaf: falseund dem GSAP-Ticker). - Routenwechsel in Next.js: nach oben scrollen (
lenis.scrollTo(0, { immediate: true })) undScrollTrigger.refresh()aufrufen, nachdem die neue Seite gemountet ist.
Vue / Nuxt – Erstelle in onMounted innerhalb von gsap.context(() => {…}, rootEl), rufe ctx.revert() in onUnmounted auf. Nuxt: Client-only-Plugin (plugins/gsap.client.ts).
Astro / statische Webseiten mit mehreren Seiten – Ein <script> im Layout; bei View Transitions erneut initialisieren auf astro:page-load und Trigger auf astro:before-swap beenden.
Vanilla – Eine main.js, die nach DOM-Ready ausgeführt wird; pro Abschnitt in gsap.context() gruppieren.
4. ScrollTrigger-Regeln, die 90% der Fehler verhindern
- Setze
scrollTriggerauf eine Timeline oder einen Tween der obersten Ebene – niemals auf Tweens, die innerhalb einer Timeline verschachtelt sind. - Verwende
ease: "none"mitscrub. - Die Reihenfolge der Erstellung ist wichtig: Erstelle Trigger von oben nach unten in der Seitenreihenfolge (oder setze
refreshPriority), besonders bei Pins. - Pinne einen Wrapper, nicht das Element, das du animierst. Pinne nicht innerhalb eines
transform-Elterns. VermeidepinSpacing: false, es sei denn, es ist beabsichtigt. - Verwende Funktionswerte (
x: () => …) +invalidateOnRefresh: truefür alles, was von der Viewport-Größe abhängt. - Rufe
ScrollTrigger.refresh()auf, nachdem Bilder/Schriftarten/dynamische Inhalte das Layout ändern (window.addEventListener("load", …),document.fonts.ready). - Debugge mit
markers: true– entferne sie vor dem Deployment. - Initial verborgene Zustände: Verwende
gsap.from/fromTooderautoAlpha, die von JS gesetzt werden, damit der Inhalt ohne JS sichtbar ist. once: truefür Enthüllungen, die nicht wiederholt werden sollen (die meisten).
5. Responsiv & reduzierte Bewegung
Umschließe jede Szene mit gsap.matchMedia():
const mm = gsap.matchMedia();
mm.add({
isDesktop: "(min-width: 1024px)",
isMobile: "(max-width: 1023px)",
motionOK: "(prefers-reduced-motion: no-preference)",
}, ({ conditions: c }) => {
if (!c.motionOK) return; // statisches Layout, keine Animation
if (c.isDesktop) { /* gepinnte / horizontale Szenen */ }
if (c.isMobile) { /* nur einfache Fade-Ups */ }
});
matchMedia setzt automatisch zurück, wenn sich die Bedingungen ändern – keine manuelle Bereinigung nötig.
6. Performance
- Animiere
x/y/scale/rotation/autoAlpha(Transforms + Opacity). Vermeide das Animieren vonwidth/height/top/leftund starkemfilter: blur()auf großen Flächen. - Bevorzuge eine Timeline pro Abschnitt gegenüber Dutzenden separaten Triggern;
ScrollTrigger.batchfür viele identische Enthüllungen. gsap.quickTofür Pointer-Follow-Effekte.- Große Bilder in gescrubbten Szenen: korrekte
sizesund vermeide das Skalieren großer Bitmaps in jedem Frame.
7. Überprüfung
Scrolle die ganze Seite schnell hoch und runter; ändere die Fenstergröße; lade mitten auf der Seite neu; navigiere weg und zurück (SPA) – keine Sprünge, keine doppelten Pins, keine Konsolenfehler. Teste mit aktivierter reduzierter Bewegung, bei 375px und mit CPU-Drosselung.
Rezepte für häufige Effekte: ../motion-direction/references/patterns.md.