Page Transitions & Shared ElementPage Transitions & Shared Element

Journal

Page Transitions & Shared Element

Preloader curtain, cobalt wipe and Shared Element docking: behind the scenes of fluid page transitions with Next.js App Router, GSAP and Lenis.

Across the standard web, page navigation is almost always a jarring cut: click a link, watch the viewport flash or blank out, then wait for new content to drop in. On this portfolio, I wanted the exact opposite: an experience reminiscent of a native operating system where every transition sustains motion and guides focus smoothly.

1. The initial preloader: loading with purpose

Before navigating between pages, there is the initial visit. Rather than letting users witness font pop-ins or WebGL shader compilation hiccups above the fold, I built an intentional preloader curtain.

This preloader does not idle on arbitrary timeouts: it tracks genuine system promises. It waits for fonts (document.fonts.ready), critical above-the-fold imagery, and on desktop devices, the dynamic instantiation of our WebGL fluted-glass and 3D monogram modules.

Thresholds are strictly bounded: a 700ms floor ensures the composition and typography can be appreciated, paired with a 2200ms hard ceiling so slow connections never get held hostage. Once completed, a sessionStorage token remembers the state so repeat visits skip straight in.

Loading screen
The cobalt preloader curtain: synchronizing web fonts, critical imagery, and WebGL shaders.

2. Standard transition: cobalt wipe & variable typography

For high-level routing (Home to About or Contact), navigation fires an energetic cobalt curtain wipe. On click, the curtain slides up from bottom to top using an aggressive expo.inOut easing curve.

The subtle hurdle here was variable typography interpolation: morphing the destination label from Archivo Thin (wght 150) to Archivo Fat (wght 800) during the wipe. In bold cuts, words expand in width and can suddenly wrap onto a second line midway through animation, breaking composition.

The fix involved pre-measuring the label in its widest bold state inside a hidden DOM sandbox to permanently lock line breaks in place. The font-variation-settings interpolation then glides without an ounce of layout shift.

3. Shared Element transitions with Next.js App Router

When exploring projects or journal articles, a solid wipe would feel disconnected. The ideal flow mirrors polished mobile applications: tapping a card thumbnail and watching it expand seamlessly into the article's hero banner.

In Next.js App Router, this is notoriously tricky: changing routes immediately unmounts the source page component tree. If you wait for the target page to mount, your source thumbnail is already gone.

To bridge that unmount, the transition runs across 4 distinct beats:

1. On click interception, we measure the thumbnail's exact viewport coordinates and bounding box via getBoundingClientRect().

2. We immediately clone the <img> element into a detached floating layer pinned directly to document.body, copying its exact computed border-radius and pixel coordinates.

3. GSAP animates this clone into full viewport dimensions (100vw × 100vh) over 0.8 seconds. While this floating canvas shields the screen, Next.js executes router.push() in the background.

4. As soon as the destination page mounts, a [data-shared-target] attribute identifies the hero cover box. The clone glides into these target coordinates (docking) before clean removal from the DOM.

4. Lenis scroll coordination & accessibility

Lenis smooth scrolling feels incredible, but during page transitions it can easily cause coordinate drift if scroll momentum keeps rolling while elements interpolate.

We established strict lifecycle dispatching: when navigation fires (wu:nav-start), Lenis stops immediately. When the curtain or clone covers the viewport (wu:nav-cover), scroll position silently snaps to zero. Upon completion (wu:nav-end), Lenis resizes and unpauses.

Finally, the entire pipeline observes prefers-reduced-motion: if a visitor requests reduced motion at the OS level, all transition timelines are instantly bypassed for straightforward, immediate routing.