How to Use the View Transitions API in JavaScript

The View Transitions API provides a native mechanism for creating seamless visual transitions between different DOM states or pages. This article explains what the View Transitions API is, how it simplifies animated UI states without complex third-party libraries, and how JavaScript works alongside CSS pseudo-elements to capture snapshots, update the DOM, and coordinate smooth animations.


What Is the View Transitions API?

Traditionally, animating changes between two different states in a web application required complex JavaScript libraries, absolute positioning hacks, and manual calculation of element coordinates. The View Transitions API solves this by providing a browser-native way to animate transitions.

It takes a snapshot of the current DOM state (“old” view), pauses rendering while JavaScript updates the DOM, takes a snapshot of the updated state (“new” view), and then automatically cross-fades or animates between the two using CSS animations.


How JavaScript Coordinates the Transition

JavaScript coordinates the transition lifecycle using a single core method: document.startViewTransition().

The Basic Implementation

To initiate a transition, you pass a callback function to document.startViewTransition() that updates the DOM:

function updateContent() {
  // Check for browser support
  if (!document.startViewTransition) {
    updateDOM();
    return;
  }

  // Coordinate the transition
  const transition = document.startViewTransition(() => {
    updateDOM();
  });
}

function updateDOM() {
  const container = document.querySelector("#content");
  container.textContent = "New content loaded dynamically.";
}

The Lifecycle Steps

When document.startViewTransition() is called, the browser executes the following sequence:

  1. Capture Old State: The browser captures a screenshot of the elements marked for transition.
  2. DOM Update Callback: The callback function passed to startViewTransition runs. If the callback returns a Promise, the browser waits for the Promise to resolve (allowing for asynchronous data fetching or template rendering).
  3. Capture New State: The browser captures a screenshot of the new DOM state.
  4. Construct Pseudo-Element Tree: The browser creates a pseudo-element tree representing the old and new states.
  5. Animate: The browser animates the transition from the old screenshot to the new screenshot using standard CSS animations (a cross-fade by default).
  6. Clean Up: Once the animation completes, the pseudo-elements are removed from the DOM.

Handling Asynchronous DOM Updates

If your DOM update relies on asynchronous operations, such as fetching data from an API, return a Promise inside the callback:

async function navigateToPage(url) {
  if (!document.startViewTransition) {
    await fetchAndRender(url);
    return;
  }

  const transition = document.startViewTransition(async () => {
    await fetchAndRender(url);
  });

  // Optional: Wait for the transition animation to finish
  await transition.finished;
  console.log("Transition animation has completed.");
}

The startViewTransition() method returns a ViewTransition object with several Promises: * transition.updateCallbackDone: Resolves when the DOM update function finishes. * transition.ready: Resolves when the pseudo-element tree is created and animations are about to run. * transition.finished: Resolves when all animations have completed and the UI is fully interactive.


Customizing Animations with CSS

While JavaScript coordinates the timing and state changes, CSS controls the visual presentation.

By default, the entire page (:root) undergoes a cross-fade transition. You can assign unique transitions to specific elements by giving them a view-transition-name:

.card-header {
  view-transition-name: card-header;
}

The browser automatically handles the size, position, and opacity changes for any element with an assigned view-transition-name, interpolating between the old and new positions seamlessly.

You can customize the animation using the generated pseudo-element tree:

::view-transition-old(card-header) {
  animation: slide-out 0.3s ease-out;
}

::view-transition-new(card-header) {
  animation: slide-in 0.3s ease-in;
}

@keyframes slide-out {
  to { transform: translateX(-100%); opacity: 0; }
}

@keyframes slide-in {
  from { transform: translateX(100%); opacity: 0; }
}

Benefits of the View Transitions API