Pin
Die Section bleibt stehen, die Seite scrollt darunter weiter.
Acht Effekte aus echten Projekten. Mit Code.
Inhalt// Hover any row in the index above
gsap.to(title, {
duration: 0.7,
overwrite: true,
scrambleText: {
text: title.dataset.text,
chars: "ABCDEFGHIJKLMNOPQRSTUVWXYZ#%&*",
revealDelay: 0.1,
},
});SplitText + ScrollTrigger
Jede Zeile kriegt ihre eigene Maske. Gedruckt wird mit der Scrollbar.
SplitText schaut nach, wo der Browser deine Zeilen tatsächlich umbricht, steckt jede in eine Maske und teilt neu, sobald Fonts nachladen oder sich das Fenster ändert. Jede Headline hier läuft so. Der Absatz rechts hängt am Scroll. Scroll ruhig wieder hoch und schau zu, wie er sich zurückdruckt.
Gute Bewegung funktioniert wie guter Satz. Sie hat Rhythmus, sie weiß, wo die Zeile bricht, und sie lässt dich nie auf die Pointe warten.
SplitText.create(paragraph, {
type: "lines,words",
mask: "lines", // each line clips itself
autoSplit: true, // re-split on resize & font load
onSplit(self) {
return gsap.timeline({
scrollTrigger: { trigger: paragraph, start: "top 80%", end: "bottom 35%", scrub: 1 },
})
.from(self.lines, { yPercent: 100, stagger: 0.1 })
.fromTo(self.words, { opacity: 0.12 }, { opacity: 1, stagger: 0.02 }, 0);
},
});Flip
Klasse tauschen, Faden behalten. Flip merkt sich, wo alles stand.
Du animierst nie auf ein Layout hin. Du tauschst die Klasse, und Flip rechnet aus, wie jede Kachel von ihrem alten Platz zum neuen kommt. Grid, Liste und Stapel sind drei schlichte CSS-Regeln. Die Bewegung gibt's umsonst.
// 1. record where everything is
state = Flip.getState(".flip__tile");
// 2. let Vue change the class
layout.value = "stack";
// 3. after the DOM updates, animate from old → new
await nextTick();
Flip.from(state, {
duration: 0.9,
ease: "power3.inOut",
stagger: 0.04,
absolute: true,
});Draggable + InertiaPlugin
Bei Oberflächen zum Anfassen zählt vor allem das Loslassen.
Draggable kümmert sich um Maus, Touch und Ränder. InertiaPlugin misst, wie schnell du loslässt, und lässt jeden Sticker ausgleiten, mit Abprall an den Kanten. Ein bisschen Schräglage unterwegs, ein kleines Nachwackeln bei der Landung, und auf einmal ist es Papier.
Packen und werfen
Draggable.create(".sticker", {
bounds: desk,
inertia: true, // InertiaPlugin: momentum on release
edgeResistance: 0.7,
onDrag() {
// tilt into the direction of travel
gsap.to(this.target, { rotation: this.deltaX * 1.6 });
},
onThrowComplete() {
gsap.to(this.target, { rotation: gsap.utils.random(-12, 12), ease: "elastic.out(1, 0.5)" });
},
});Raw WebGL
Kein three.js, keine Texturen. Ein Fragment-Shader und drei Farben.
Rauschen, das in sich selbst verbogen wird, ergibt diese langsamen, tintigen Wellen. Ich drück das Ganze auf Papier, Schwarz und Rot runter und streu Korn drüber, das sich jeden Frame ändert. Dann flimmert es wie ein echter Risodruck. Deine Maus schiebt die Tinte herum. Gefällt's dir, macht die Export-Seite ein Poster draus.
Mach ein Poster draus// fragment shader: domain-warped fbm
vec2 q = vec2(fbm(uv * 1.6 + t), fbm(uv * 1.6 + vec2(5.2, 1.3) - t));
float pull = exp(-length(uv - mouse) * 3.5);
vec2 r = vec2(fbm(uv * 1.6 + 4.0 * q + pull), fbm(uv * 1.6 + 4.0 * q - pull));
float f = fbm(uv * 1.6 + 4.0 * r) + grain * 0.07;
// posterize into three spot inks
vec3 col = f < 0.37 ? ink : f < 0.47 ? accent : paper;ScrollTrigger · containerAnimation
Du scrollst runter. Die Geschichte läuft nach links.
Die Section unten bleibt stehen und schiebt vier Panels durchs Bild, während du weiterscrollst. Jedes Riesenwort hat einen eigenen Trigger, gemessen an der Seitwärtsbewegung statt an der Seite. Deshalb driftet und kippt es beim Durchlaufen.
const slide = gsap.to(track, {
x: () => -(track.scrollWidth - innerWidth),
ease: "none",
scrollTrigger: { trigger: section, pin: true, scrub: 1, invalidateOnRefresh: true },
});
// nested triggers ride the horizontal tween
gsap.fromTo(word, { xPercent: 20 }, {
xPercent: -20,
scrollTrigger: { trigger: word, containerAnimation: slide, scrub: true },
});Die Section bleibt stehen, die Seite scrollt darunter weiter.
Der Abspielkopf der Timeline hängt direkt an der Scrollbar.
Kind-Trigger messen an der Seitwärtsbewegung, nicht an der Seite.
Pin lösen, Scroll zurückgeben, weiter zum Nächsten.
gsap.ticker · utils.wrap
Ein Laufband mit Tacho.
Ein einziger Ticker-Callback läuft deinem Scrolltempo hinterher, lässt jede Zeile bei halber Breite umlaufen und legt die Schrift schräg, je schneller du bist. Hörst du auf, trudelt alles langsam aus. Das Band unter dem Cover der Startseite ist genau diese Komponente.
gsap.ticker.add((_t, delta) => {
velocity += (scrollY - lastY - velocity) * 0.12;
lastY = scrollY;
offset += dir * (0.6 + Math.abs(velocity) * 0.5) * (delta / 16.67);
offset = gsap.utils.wrap(-row.scrollWidth / 2, 0, offset);
row.style.translate = `${offset}px 0`;
setSkew(gsap.utils.clamp(-14, 14, -velocity * 0.4));
});Variable fonts · CSS custom properties
Bricolage Grotesque hat eine Gewichts- und eine Breitenachse. Beide hängen am Abstand.
Jeder Buchstabe trägt zwei Custom Properties, die in font-variation-settings landen. GSAP blendet sie Buchstabe für Buchstabe über, je nachdem, wie weit deine Maus weg ist. Die Schrift wird dünn und schmal, wo du hinschaust. Der große Name auf der Startseite ist dieselbe Komponente.
Komm näher.
const weight = gsap.utils.pipe(
gsap.utils.clamp(0, radius),
gsap.utils.mapRange(0, radius, 200, 800),
);
// .px-char { font-variation-settings: "wght" var(--wght), "wdth" var(--wdth) }
for (const c of chars) {
const d = distance(pointer, center(c));
gsap.to(c, { "--wght": weight(d), "--wdth": width(d), overwrite: "auto" });
}Canvas 2D · lenticular
Das Wackelbild aus der Cornflakes-Packung, nachgebaut.
Beide Motive werden unsichtbar vorgemalt und dann jeden Frame als ineinander verschränkte Sieben-Pixel-Streifen neu gezeichnet. Jeder Streifen sucht sich seine Seite aus Blickwinkel und eigener Position. Deshalb kippt das Bild in einem Wisch und nicht auf einmal. Ein Rillenmuster und ein wandernder Glanz machen den Rest.
Fahr über den Druck
for (let x = 0; x < w; x += strip) {
// each lens flips a little later than its neighbour → a sweep
const local = angle + (x / w - 0.5) * 0.6;
const t = clamp(0, 1, (local + 0.15) / 0.3);
ctx.globalAlpha = 1;
ctx.drawImage(sideA, x, 0, strip, h, x, 0, strip, h);
ctx.globalAlpha = smoothstep(t);
ctx.drawImage(sideB, x, 0, strip, h, x, 0, strip, h);
}
// ridges: a strip-wide gradient repeated as a pattern
ctx.fillStyle = ctx.createPattern(lens, "repeat");
ctx.fillRect(0, 0, w, h);Library
Weiterlesen
Bricolage Grotesque, Instrument Serif, JetBrains Mono
GSAP 3: SplitText, ScrambleText, Flip, Draggable, Inertia, ScrollTrigger · Canvas 2D · WebGL
Lenis, synchron zum GSAP-Ticker
Nuxt 3 · Vue 3 · Tailwind