October 6, 2026

GSAP in Webflow: So sind die Scroll-Animationen von sxlvx.de gebaut

Seit heute steht sxlvx.de in der offiziellen GSAP Gallery. Schön. Aber eine Galerie zeigt nur das Ergebnis. Hier steht, wie es gebaut ist: mit echtem Code, echten Fehlern und echten Zahlen. Für alle, die in Webflow mehr wollen als Fade-in von unten.

Die Idee: Jede Leistung bekommt eine eigene Szene

Auf den Leistungsseiten scrollst Du nicht an Text vorbei. Du schaust zu, wie die Arbeit entsteht. Beim Imagefilm fährt die Kamera durchs Studio, beim Branding entsteht ein Logo aus Hilfslinien, beim Webdesign wird aus Fragen ein Wireframe und daraus eine fertige Seite. Ein Mauszeiger mit meinem Namen führt durch alles.

Technisch ist jede Szene dasselbe Muster: ein HTML Embed in Webflow, eine GSAP Timeline, ein ScrollTrigger mit scrub. Sonst nichts.

1. Webflows GSAP nutzen, nicht ein zweites laden

Webflow liefert GSAP inklusive ScrollTrigger und SplitText inzwischen selbst aus. Ich hatte trotzdem in jedem Embed eine eigene Version von jsDelivr eingebunden. Ergebnis: zwei GSAP-Kopien, zwei ScrollTrigger, doppelte Event-Listener. Im mobilen PageSpeed-Test lag die Total Blocking Time bei 15,9 Sekunden.

Das Problem dabei: Webflow lädt sein GSAP erst am Seitenende. Ein Embed weiter oben findet beim Ausführen noch kein gsap. Die Lösung ist ein kleiner Start-Wrapper, der auf das Dokument wartet:

(function(){
  var me = document.currentScript;
  var sec = me && me.closest('.szene');   // Referenz sofort sichern
  function boot(){
    if(!sec || !window.gsap || !window.ScrollTrigger) return;
    if(matchMedia('(prefers-reduced-motion: reduce)').matches) return;
    gsap.registerPlugin(ScrollTrigger);
    // ... Szene bauen
  }
  if(document.readyState === 'loading')
    document.addEventListener('DOMContentLoaded', boot);
  else boot();
})();

Wichtig ist die erste Zeile: document.currentScript gibt es nur während das Script läuft. Später, im Event-Listener, ist es weg. Deshalb wird die Section vorher gesichert.

2. Sticky statt pin

Für lange Scroll-Szenen nutze ich kein pin: true, sondern CSS. Die Section ist rund 600vh hoch, der Inhalt darin klebt mit position: sticky. ScrollTrigger muss dann nur noch den Fortschritt liefern:

.szene.on { height: 620vh; }
.szene.on .pin { position: sticky; top: 0; height: 100vh; overflow: hidden; }

tl = gsap.timeline({
  defaults: { ease: 'power3.out' },
  scrollTrigger: { trigger: sec, start: 'top top', end: 'bottom bottom', scrub: 1 }
});

Der Vorteil: kein Pin-Spacer, kein Springen beim Refresh, und die Klasse .on kommt erst per JavaScript dazu. Ohne JavaScript oder mit reduzierter Bewegung bleibt die Section eine normale, kurze Inhaltsseite.

3. Erst messen, dann animieren

Der Mauszeiger soll exakt auf Elemente zeigen, die selbst gerade animiert werden. Misst Du während der Timeline gebaut wird, misst Du verschobene oder skalierte Elemente. Also messe ich alles einmal im Ruhezustand, bevor der erste Tween existiert:

var PR = pin.getBoundingClientRect(), M = new Map();
function raw(el){
  var r = el.getBoundingClientRect();
  return { l: r.left - PR.left, t: r.top - PR.top, w: r.width, h: r.height,
           cx: r.left - PR.left + r.width / 2, cy: r.top - PR.top + r.height / 2 };
}
sec.querySelectorAll('.pin *').forEach(function(el){ M.set(el, raw(el)); });
var R = function(el){ return M.get(el); };

Bei einem Resize wird die Timeline mit tl.revert() zurückgesetzt, neu gemessen und neu gebaut. Nur bei echter Breitenänderung, sonst feuert die Adressleiste am Handy ständig neu.

4. Der Cursor als Erzähler

Damit eine Szene lesbar bleibt, gibt es eine Handvoll Helfer. Hinfahren, klicken, Werkzeug wechseln:

var go = function(x, y, t, d){ tl.to(cur, { x: x, y: y, duration: d, ease: 'power2.inOut' }, t); };
var click = function(t){ tl.to(ico, { scale: .78, duration: .25, yoyo: true, repeat: 1 }, t); };
var tool = function(name, t){ tl.set(cur, { attr: { 'data-i': name } }, t); };

// Logo-Hälfte setzen: hinfahren, klicken, Form wächst
go(r.cx, r.cy, 40.6, .8); click(41.4);
tl.from('.mark-a', { scale: 0, transformOrigin: '50% 100%', ease: 'back.out(1.8)' }, 41.5);

Die Zeitangaben sind keine Sekunden, sondern Positionen auf einer Timeline mit rund 100 Einheiten. Durch scrub entspricht das am Ende Scrollstrecke. So plane ich eine Szene wie ein Storyboard: bei 13 erscheint das Workshop-Board, bei 33 die Hilfslinien, bei 87 das Ende.

5. Skalieren mit Container Query Units

Die Szenen sind keine Videos, sondern echtes HTML und SVG. Damit sie auf jeder Breite gleich aussehen, rechnet alles in cqw, also Prozent der Rahmenbreite. Ein Haken hat mich eine Stunde gekostet: cqw am Container selbst bezieht sich nicht auf den Container. Der Wert muss auf den Kindern definiert werden:

@property --u { syntax: '<length>'; inherits: true; initial-value: 0px; }
.art { container-type: size; }
.art > * { --u: 1.3cqw; }              /* auf den Kindern, nicht am Container */
.fenster { border-radius: calc(var(--u) * 1.2); }
@media (max-width: 767px) { .art > * { --u: 1.9cqw; } }
6. Der 3D-Spatz: aus dem Logo statt aus einem Video

Auf der Startseite dreht sich der Spatz in Chrom. Früher war das ein Video, je nach Browser 1,7 bis 1,9 MB. Heute ist es der Umriss aus dem Logo als JSON, mit three.js zu einem 3D-Körper extrudiert und mit GSAP gedreht. three.js selbst kommt mit rund 170 KB, der Rest ist ein Embed mit wenigen KB.

const s = new THREE.Shape(punkte.map(([x, y]) => new THREE.Vector2(x, y)));
const g = new THREE.ExtrudeGeometry(s, { depth: .5, bevelEnabled: true, bevelSize: .035 });

gsap.to(st, { spin: Math.PI * 2, duration: 7, ease: 'none', repeat: -1 });
const qx = gsap.quickTo(st, 'tx', { duration: .9, ease: 'power3' });
addEventListener('pointermove', e => qx((e.clientY / innerHeight - .5) * .35));

Was ich gelernt habe: overwrite: true beim Scroll-Boost hat die Endlos-Drehung gekillt. overwrite: 'auto' überschreibt nur die Eigenschaften, die sich tatsächlich überschneiden.

Und: Der Spatz läuft nur auf Desktop mit echter Grafikkarte. Bis das Script entscheidet, zeigt die Seite ein Standbild. Der PageSpeed-Test hat keine GPU und rechnet WebGL in Software, dort hat jeder Frame rund 130 ms gekostet. Deshalb prüfe ich vorher:

function gpuOK(){
  if(!matchMedia('(min-width: 992px) and (pointer: fine)').matches) return false;
  const gl = document.createElement('canvas').getContext('webgl');
  if(!gl) return false;
  const d = gl.getExtension('WEBGL_debug_renderer_info');
  const r = d ? gl.getParameter(d.UNMASKED_RENDERER_WEBGL) : '';
  return !/swiftshader|llvmpipe|software/i.test(r);
}
7. Die ehrlichen Zahlen

Mobiler PageSpeed vor und nach den beiden Änderungen oben, gemessen am selben Tag:

  • Total Blocking Time: von 15,9 s auf 0,73 s
  • Speed Index: von 12,9 s auf 6,7 s
  • Performance Score: von 31 auf 46

46 ist kein guter Wert. Der Rest steckt in Videos, nicht in GSAP. Und da wartet die nächste Überraschung: Webflow kodiert Background Videos nach dem Upload selbst neu. Ein Clip, den ich auf 43 KB komprimiert hatte, wurde auf der Live-Seite zu 195 KB als mp4 und 549 KB als webm. Komprimieren vor dem Upload bringt beim Background Video also nichts. Die Lösung ist ein eigenes Video-Tag im Embed, das erst lädt, wenn es in Sicht kommt.

Was ich mitnehme

GSAP ist nicht das Performance-Problem. Doppelte Bibliotheken, WebGL ohne GPU und schwere Videos sind es. Wer Animation ernst nimmt, misst nicht nur im eigenen Browser, sondern dort, wo es weh tut: im mobilen Test mit gedrosselter CPU.

Animation ist keine Dekoration. Sie erzählt, was Du machst, bevor jemand den Text liest.
Häufige Fragen

Kann ich GSAP in Webflow ohne eigenes Hosting nutzen?
Ja. Webflow liefert GSAP mit ScrollTrigger und SplitText mit. Du aktivierst es in den Site Settings und schreibst Deinen Code in ein Embed oder in den Custom Code der Seite. Ein zusätzliches Einbinden per CDN ist dann überflüssig und kostet Ladezeit.

Warum findet mein Embed kein gsap?
Weil Webflow GSAP am Ende der Seite lädt, Dein Embed aber weiter oben ausgeführt wird. Starte Deinen Code erst nach DOMContentLoaded und sichere vorher document.currentScript.

Pin oder position: sticky?
Für lange, scrubbende Szenen nutze ich sticky. Weniger Layout-Arbeit für ScrollTrigger, kein Pin-Spacer, und ohne JavaScript bleibt der Inhalt normal lesbar.

Schaden Scroll-Animationen dem SEO?
Nicht, wenn der Inhalt echtes HTML ist und ohne JavaScript sichtbar bleibt. Problematisch werden schwere Videos, blockierende Scripts und Inhalte, die erst nach einer Animation erscheinen.

Genug gesehen? Dann lass uns reden.

Genug gesehen? Dann lass uns reden.

Genug gesehen? Dann lass uns reden.

Genug gesehen? Dann lass uns reden.

Projekt anfragen

Projekt anfragen