Start implementing view transitions on your websites today

The View Transition API allows us to animate between two states with relative ease. I say relative ease, but view transitions can get quite complicated fast.

A view transition can be called in two ways; if you add a tiny bit of CSS, a view transition is initiated on every page change, or you can initiate it manually with JavaScript.

@view-transition { navigation: auto; }

if (document.startViewTransition()) { document.startViewTransition(() => { filterItems() }) } else { filterItems() }

When a view transition is initiated it creates two snapshots; one of the current state, and one the future state. The view transition then compares the position, sizing and rotation of the two snapshots and, finally creates a keyframe animation. It’s pretty much how the FLIP animation technique works, but CSS does all the heavy lifting, even if JavaScript initiates the view transition.

Those snapshots end up in a view transition pseudo element, and by default only a snapshot of the root (HTML) element is created, but you can add more items to the pseudo element by adding view-transition-names to elements.

Anatomy of a view transition

::view-transition ::view-transition-group(root) ::view-transition-image-pair(root) ::view-transition-old(root) ::view-transition-new(root)

Every named view transition gets its own group. Every group has a view-transition-image-pair and a view-transition-old and/or view-transition-new pseudo element.

  • The view transition group is where the custom matrix animation is added to animate to the new state’s position, rotation and size.
  • The view transition image pair is used to isolate the mix-blend-mode animation that’s on the view-transition-old and view-transition-new states by default.
  • The view-transition-old and view-transition-new states represent the old and new state of the named element.

The snapshots are no longer HTML because it’s a non interactive snapshot of the element as it was when it was captured by the initiation of the view transition. Adding CSS to the view transition selector to change font size or colour, for example, or trying to change the content with JavaScript during the view transition doesn’t work.

Debugging view transitions

You can debug your view transitions with the Animations Drawer in the Chrome Dev Tools, this drawer allows you to slow down animations and even to pause animations, which really gives you some time to inspect what’s going on.

Use CMD + Shift + p in the dev tools and type animations to open up the Animations Drawer. For Windows users, switch CMD with CTRL.

The animations drawer as described above

Unique view transition names and match-element

Adding an element to the view transition pseudo element is easy: give it a unique name with view-transition-name.

For Same Document View Transitions you can set the view-transition name to match-element, but if you animate between pages you have to manually add unique view transition names to the elements on both pages.

Setting up your project for view transitions

View transitions can be used to animate filtering, sorting, add to cart, page transitions, and much more, but when you start doing multiple view transitions triggered by different elements and different user interactions, it’s going to be hard to see the forest through the trees.

View Transition Types are the answer here, you can add types through JavaScript when you call a view transition:

if (document.startViewTransition) { document.startViewTransition({ update: () => filterItems(), types: ['filter'] }) } else { filterItems() }

The types are added as a pseudo-class :active-view-transition-type(filter) that you can use to encase your specific styles for that interaction. This is very helpful if you want to have different animations on filter and page transition for example.

For specific page transitions between overview and detail pages we can also add a view transition type, but it’s a little more complicated because we currently need to use JavaScript to watch for the pagereveal event and check the from and entry url. Page reveal is called when a user navigates to a new page.

window.addEventListener('pagereveal', async (e) => { if (e.viewTransition) { let transitionType = 'normal' if (navigation?.activation?.from && navigation?.activation?.entry) { transitionType = determineTransitionType(navigation.activation.from, navigation.activation.entry) } e.viewTransition.types.add(transitionType) } }) const determineTransitionType = (from, to) => { const currentUrl = from?.url ? new URL(from.url) : null const targetUrl = new URL(to.url) let currentPath = currentUrl.pathname let targetPath = targetUrl.pathname const fromType = getPageType(currentPath) const toType = getPageType(targetPath) return `${fromType}-to-${toType}` } const getPageType = (path) => { path = path.replace('/', '') const segments = path.split('/') const [firstSegment, secondSegment] = segments switch (firstSegment) { case '': return 'home' case 'work': case 'blog': return secondSegment ? `${firstSegment}-detail` : `${firstSegment}-overview` default: return 'normal' } }

Going from /work/ to /work/piccalilli would return work-overview-to-work-detail as a view transition type.

AdvertJavaScript for Everyone. Available now. Save £60 using code JS4ELAUNCH until October 28

Good defaults and best practices

View transitions run on CSS keyframes, adding some default styling can make your animations more sturdy.

::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation-duration: 0.6s; animation-fill-mode: forwards; animation-timing-function: var(--default-ease); }

This will give your animations the same base duration, fill mode and ease.

Use view-transition-class to be able to style multiple items at once, and set all custom animations without using the animation shorthand so you don’t override any of your defaults without meaning to.

::view-transition-old(.work-item) { animation-name: scale-out; animation-duration: 0.4s; }

While a long animation might look cool, it will probably be very annoying to your users because the page will be blocked from interaction until the full animation is done.

Also, set your unique view transition names inline on your HTML with a CSS variable.

<article class="work-item" style="--vt: work-item-1"> </article>

.work-item { @media (prefers-reduced-motion: no-preference) { view-transition-name: var(--vt); } view-transition-class: work-item; }

This approach has several advantages:

  • It’s easier to create unique view transition names, especially if you’re using a CMS or framework, using an id or index value in the name.

  • It keeps your HTML from looking like a framework boilerplate like Tailwind because --vt is nice and short

  • If you have multiple view transition types you can conditionally add the items to the pseudo element easier.

    html:active-view-transition-type(filter) { .work-item { @media (prefers-reduced-motion: no-preference) { view-transition-name: var(--vt); } view-transition-class: work-item; } }

  • If you want to add all view transition names at once you can use an attribute selector:

    [style*="--vt"] { @media (prefers-reduced-motion: no-preference) { view-transition-name: var(--vt); } }

Users with vestibular disorders can get sick from all of that slick movement on your website, so make sure you’re mindful of that.

The best thing to do is encase all your view-transition-name declarations in a prefers reduced motion media query set to no-preference which will default all view-transitions back to just cross-fading the root element.

@media (prefers-reduced-motion: no-preference) { view-transition-name: var(--vt); }

As I mentioned earlier, view transition groups consist of a view-transition-image-pair which has either view-transition-old, view-transition-new or both. Whether there is an old and/or a new state depends on if the named view-transition element exists in the old state and the new state.

In practice this means that for animations that change the order, like sorting, items will have an old and a new state, because they exist in the old state and the new state.

During the view transition, by default, the view-transition-old pseudo element crossfades into the view-transition-new state, represented by the red and green rectangle respectively.

If you filter items out, those items will only have a view-transition-old state because they do not exist in the new state:

In the same way, if filters add items back in, they will only have a view-transition-new state because they did not exist in the old state:

With CSS we can check if view-transition-old or view-transition-new is an only child using the :only-child pseudo-class, allowing us to create in-and-out animations for them.

::view-transition-old(work-item):only-child { animation-name: animate-out; animation-duration: 0.3s; } ::view-transition-new(work-item):only-child { animation-name: animate-in; animation-duration: 0.3s; }

Try out this demo. When you apply sorting, the in-and-out animations are not triggered but when you filter they are!

This is pretty cool for filtering, but also when you want to transition from an overview to a detail page or back like I did for this website I created for my CSS Day Talk. In this talk I also mentioned we’re currently not able to use :has to check if a group has both view-transition-old and -new elements, because this would allow us to set a higher z-index on items that have both, and push them in front of the other elements. But complaining on a stage helps because that’s now been added as an issue!

Browser compatibility

With the release of Firefox 144, the View Transition API has now been implemented in all the ‘big browsers’ 🥳.

Unfortunately, though, Firefox currently only supports same-document view transitions, not transitions between pages (also known cross-document view transitions).

If you want to follow along when they will support that, you can check MDN or this handy CodePen that the Chrome team created.

Enjoyed this article? You can support us by leaving a tip via Open Collective

AdvertJavaScript for Everyone. Available now. Save £60 using code JS4ELAUNCH

Freelance Creative Developer, tech speaker and teacher at the Amsterdam University of Applied Sciences.

- 위키
Copyright © 2011-2025 iteam. Current version is 2.147.1. UTC+08:00, 2025-10-29 23:24
浙ICP备14020137号-1 $방문자$