LOG 01 · ANIMATION

GSAP & Scroll-Driven Animation

How GSAP drives scroll-linked motion and section navigation across my builds — from ScrollTrigger reveals to a fully custom, camera-driven scroll system.

Tweens, not keyframes

Every motion on my sites — panel reveals, hover states, section transitions — is a GSAP tween, not a CSS keyframe animation. Tweens are addressable: they can be killed mid-flight, chained, reversed, or re-targeted in response to user input, which keyframe animations can't do without a pile of class-toggling workarounds.

Easing is chosen deliberately per interaction, not left at a default. power2.inOut for camera moves that need to feel weighted at both ends, back.out(1.2) for UI elements that should overshoot slightly and settle — the difference is subtle but it's the gap between motion that feels designed and motion that feels like a plugin default.

Beyond ScrollTrigger: a camera-driven scroll system

ScrollTrigger ties animation progress to real document scroll position — perfect for most sites. This one's homepage doesn't use real document scroll at all: each wheel or swipe gesture drives a Three.js camera along a 3D path, and GSAP tweens the camera's position directly rather than tying it to scrollTop.

That required replacing ScrollTrigger's onEnter/onLeave callbacks with a small pub/sub store (a subscribe/setActiveIndex pattern) that every section-aware component listens to. Components that used to key off scroll position — an entrance reveal, a nav background toggle, an autoplay trigger — now key off 'which section index is active', decoupling animation logic from any specific scroll implementation underneath it.

The payoff: sections can transition via a virtual camera, a scrollbar, a swipe gesture, or a click — the same completion signal reaches every listener regardless of which input triggered it.

Responsive by breakpoint, not by guesswork

gsap.matchMedia() scopes animation setup to specific breakpoints, so a desktop hover-driven reveal and a mobile tap-driven one can coexist in the same component without runtime checks scattered through the logic — mobile users never pay the cost (in JS execution or in layout) of a desktop-only interaction.