Clean Markup

Seamless Page Navigation with Cross-Document View Transitions

Seamless Page Navigation with Cross-Document View Transitions

For years, web developers reached for client-side single-page application (SPA) routing frameworks primarily to avoid the abrupt blank-screen flash associated with standard multi-page navigations. While the View Transitions API introduced programmatic DOM morphing via JavaScript for SPAs in 2023, traditional multi-page architectures (MPAs), such as static site generators like Eleventy and server-rendered applications, remained locked out of native page morphing without heavy JavaScript bundles. In 2024, the web platform eliminated this disparity with Cross-Document View Transitions, introducing a purely declarative mechanism allowing seamless transitions between distinct HTML documents.

By placing @view-transition { navigation: auto; } into shared stylesheets, browsers automatically capture snapshots of the outgoing page and incoming page during same-origin navigations. The browser synthesizes pseudo-elements representing old and new states (::view-transition-old and ::view-transition-new), animating cross-fades by default or morphing individual elements between documents when paired with corresponding view-transition-name declarations. When combined with modern accessibility practices through media queries that respect reduced motion preferences, developers can achieve native app-like visual continuity across static HTML sites without sacrificing performance, battery efficiency, or resilience.

Example

/* Enable cross-document transitions across same-origin navigations */
@view-transition {
  navigation: auto;
}

/* Pair elements across outgoing and incoming documents */
.product-card-image {
  view-transition-name: product-hero;
}

/* Customize transition timing or pseudo-elements */
::view-transition-group(product-hero) {
  animation-duration: 350ms;
  animation-timing-function: cubic-bezier(0.2, 0, 0, 1);
}

Implementation

<!-- shared-layout.html: Include shared CSS across all pages -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Cross-Document View Transitions</title>
  <style>
    /* 1. Declaratively activate transitions for same-origin document loads */
    @view-transition {
      navigation: auto;
    }

    /* 2. Assign unique transition names to persistent elements */
    header.site-header {
      view-transition-name: site-header;
    }

    .article-hero-image {
      view-transition-name: article-featured-image;
      contain: layout;
    }

    /* 3. Style default cross-fade animations on the root pseudo-element */
    ::view-transition-old(root) {
      animation: 250ms ease-out both fade-out;
    }

    ::view-transition-new(root) {
      animation: 300ms ease-in both fade-in;
    }

    @keyframes fade-out {
      from { opacity: 1; transform: scale(1); }
      to { opacity: 0; transform: scale(0.98); }
    }

    @keyframes fade-in {
      from { opacity: 0; transform: scale(1.02); }
      to { opacity: 1; transform: scale(1); }
    }

    /* 4. Fine-tune morphing physics for named element groups */
    ::view-transition-group(article-featured-image) {
      animation-duration: 400ms;
      animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
    }

    /* 5. Essential Accessibility: Disable animations when motion is reduced */
    @media (prefers-reduced-motion: reduce) {
      ::view-transition-group(*),
      ::view-transition-old(*),
      ::view-transition-new(*) {
        animation: none !important;
      }
    }
  </style>
</head>
<body>
  <header class="site-header">
    <nav>
      <a href="/">Clean Markup Home</a>
      <a href="/articles/">Articles</a>
    </nav>
  </header>
  <main>
    <article>
      <img
        class="article-hero-image"
        src="/img/posts/html-cross-document-view-transitions.webp"
        alt="Mountain landscape"
        width="667"
        height="518"
      >
      <h1>Seamless Multi-Page Navigation</h1>
      <p>This document navigates to destination pages with native browser transitions.</p>
    </article>
  </main>
</body>
</html>

Main Point: Declarative cross-document view transitions allow static multi-page websites to render smooth element morphing and fluid navigations without any client-side JavaScript routing runtime.

Credits

Photo by Eberhard Grossgasteiger on Unsplash

← Home