intro animation
I wanted the site to feel personal from the first second — not a template fade-in. So I built a layered intro: a hand-drawn signature on load, a curved bubble curtain on navigation, and smooth scroll underneath.
The idea
Three cases, three behaviors:
Hard reload on home → signature draws, fades, done
First visit → signature on dark overlay, then bubble wipes up underneath
Other nav → quick bubble cover so you never flash the old page
Sounds clean. Getting the layers right was not.
What broke
Vivus + React — Vivus owns the DOM; React owns the DOM. Strict Mode double-mounting left ghost SVGs and half-drawn signatures. Fix: runId refs, replaceChildren() on mount, aggressive cleanup on unmount.
The blank frame — The bubble wiped before the new route painted. Fix: double requestAnimationFrame + a 600ms minimum cover so content exists before the curtain lifts.
Three scroll systems — Browser scroll, Lenis, and GSAP ScrollTrigger all had to stay in sync. Route changes kept you scrolled to the previous page; async content (images, fonts, widgets) broke trigger positions. Fix: resetScrollTop() on navigation, ResizeObserver on the body, and a debounced ScrollTrigger.refresh().
Callback restarts — An unstable onComplete re-ran Vivus mid-draw. Stable useCallback + refs fixed it.
Takeaway
The intro is the one thing every visitor sees. It took more debugging than any project page — but that’s why it doesn’t feel like a reskinned template.
Comments 0
No comments yet. Be the first!
Sign in to join the conversation.