webdevpuneet.com

Swup Page Transition — Free Hooks-Driven Fade Transition Snippet

Swup Page Transition · Animations · Plain HTML, CSS & JS · Live preview

CategoryAnimations

Copy & paste

Source Code

Requires
<div id="swup">
  <nav class="sw-nav">
    <span class="sw-logo">Journal</span>
    <a href="#entry-one" class="sw-link is-active" data-target="entry-one">Entry One</a>
    <a href="#entry-two" class="sw-link" data-target="entry-two">Entry Two</a>
  </nav>

  <div class="transition-fade" id="entry-one">
    <section class="sw-panel">
      <span class="sw-tag">Visit: entry-one</span>
      <h1>Swup in a single page</h1>
      <p>Click "Entry Two" — swup.hooks fires visit:start / content:replace / visit:end while a CSS class drives the actual fade, exactly like real Swup usage.</p>
    </section>
  </div>

  <div class="transition-fade" id="entry-two" hidden>
    <section class="sw-panel sw-panel--alt">
      <span class="sw-tag">Visit: entry-two</span>
      <h1>Second entry</h1>
      <p>On a real multi-page site this container would be replaced by fetched HTML from a second URL; here it's a sibling container toggled by the same hook sequence.</p>
    </section>
  </div>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Real Swup hooks API
visit:start/content:replace/visit:end match production.
CSS-owned timing
transition-duration on a class, not a JS tween.
In-page adaptation
Two containers stand in for two fetched pages.
Reflow-safe re-trigger
Forces layout so the enter class actually animates.
Active link state
Nav highlights the current container's link.
Console-visible lifecycle
Logs each hook as it fires for learning.
No extra animation library
Fade runs on plain CSS transitions.
Sandbox-safe
Runs standalone without a server or router.

About this UI Snippet

Swup Page Transition — Hooks and CSS-Class Timing on In-Page Containers

Screenshot of the Swup Page Transition snippet rendered live

Swup animates real page-to-page navigation: it intercepts internal link clicks, fetches the destination page's HTML, and swaps its containers into the DOM while firing lifecycle hooks — visit:start, content:replace, visit:end — around the swap. It leans on CSS to own the actual animation: Swup toggles an is-transitioning class and waits for the CSS transition-duration on that class before continuing, rather than animating with JavaScript directly. This snippet is a single HTML document with nothing to fetch, so it adapts that exact hooks-and-class pattern onto two containers already present in the page.

What's real Swup and what's adapted

new Swup({ containers: [...] }), the swup.hooks.on(...) subscriptions, and the transition-fade / is-transitioning class convention are exactly how Swup is used in production — that class-toggle timing model is Swup's actual design, not a simplification. What's adapted is the trigger: since there's no second URL to fetch, a click handler calls swup.hooks.call(...) at the same points Swup's own router would, and toggles hidden/is-transitioning on the two sibling containers instead of Swup replacing fetched content. Drop this onto a real multi-page site and you'd delete the manual toggling — Swup's router calls the hooks and swaps content itself.

CSS owns the timing, not JavaScript

Unlike a JS-driven tween, Swup's convention is to add a class and let the browser's own transition property determine how long the animation takes; Swup (and this adaptation) just waits that same duration before proceeding. That's why .transition-fade defines the opacity/transform transition and .is-transitioning defines the "leaving" state entirely in CSS — change the transition timing function or duration in one place and the whole visit lifecycle respects it automatically.

The hook sequence

visit:start fires as a visit begins (here, on click); content:replace fires the moment the new content is swapped in (here, when the next container is un-hidden); visit:end fires once the entering transition finishes. Subscribing via swup.hooks.on is how you'd hang a loading bar, analytics ping, or scroll-reset off any point in a real Swup-powered navigation.

Where this fits

For the same navigation-transition problem solved with a different library and a transitions-array hook shape instead of an event emitter, see Barba.js page transition. For scroll-triggered rather than navigation-triggered motion, see scroll reveal grid.

Customizing it

Add more containers, change .is-transitioning's transform for a different exit direction, or hang extra behavior off content:replace (like resetting scroll position, which real Swup does by default). In production, Swup's containers option targets CSS selectors present on both the current and fetched page, so naming them consistently across templates is what makes the swap work at all.

Step by step

How to Use

  1. 1
    Add the Swup CDNInclude swup.umd.js from the CDN panel.
  2. 2
    Paste HTML, CSS, and JSA nav and two containers render, one hidden.
  3. 3
    Open the console (optional)See visit:start / content:replace / visit:end log.
  4. 4
    Click "Entry Two"The current container fades out via is-transitioning.
  5. 5
    Watch the swapThe next container fades in on the same class toggle.
  6. 6
    Adapt for productionRemove the click shim; let Swup's router own navigation.

Real-world uses

Common Use Cases

Multi-page transition prototypes
Prototype hook wiring before touching a real site, using visit:start, content:replace and visit:end exactly as production does.
Blogs and journals
Fade between entries as this demo does, with CSS owning the timing through transition-duration on a class instead of a JavaScript tween.
Agency site pairings
Pair with a parallax hero landing page so each navigation also reveals a fresh opening screen.
Library comparison
Compare with the Barba.js page transition to see two approaches to lifecycle hooks side by side.
Reflow-safe re-triggering
Learn why forcing layout before adding the enter class ensures the transition actually animates every time.

Got questions?

Frequently Asked Questions

Swup is genuinely instantiated with new Swup({ containers: [...] }) and its real hooks event emitter is used to subscribe to and fire visit:start, content:replace, and visit:end. What's adapted is only the trigger: since this sandbox has no second URL to fetch, a click handler calls those same hooks and toggles which container is visible, instead of Swup's own fetch-based router doing it — the hook names, timing, and CSS class convention are unchanged from production usage.

You would remove the manual hidden-toggling and the direct swup.hooks.call() invocations. Swup's own router would intercept clicks on internal links, fetch the destination page, replace the content inside the matching container selector, and fire the lifecycle hooks itself around that swap — your CSS transition-fade / is-transitioning classes would carry over completely unchanged, since that part of the pattern was never adapted.

That's Swup's actual design: it adds an is-transitioning (or similarly named) class and then waits for the CSS transition-duration defined on that class before proceeding to the next step, rather than running its own tween engine. This keeps the animation authored entirely in CSS — change the transition property in one place and the whole page's visit timing follows it, no JS animation values to keep in sync.

In production Swup, content:replace fires at the exact moment the fetched page's HTML has been swapped into the container, after the leaving transition finishes and before the entering transition starts. In this adaptation, it fires when the next container is un-hidden and about to animate in — the same position in the sequence, just triggered manually instead of by Swup's own DOM-replacement step.

Both solve the same problem — animated transitions between page loads — with different APIs: Swup exposes a hooks event emitter (swup.hooks.on/call) and lets CSS transition-duration drive timing via class toggles, while Barba.js uses a transitions array of leave/enter functions that each return a promise it awaits. See the companion Barba.js page transition snippet for the same in-page adaptation built on that API instead.