Adding view transitions to this site

#web-dev

If you’ve clicked around the nav on this site, you might have noticed the little box around the current page now slides across to the next link, rather than just jumping there.

It’s a small thing - but it makes the site feel a lot more like one connected thing, rather than a bunch of separate pages. And the best part… it’s (mostly) just CSS.

What are view transitions?

Normally, when you click a link, the browser throws away the old page and draws the new one. Instant swap. No in-between.

View transitions let the browser take a snapshot of the old page, load the new one, and then animate between the two. By default that’s a quick cross-fade - but you can tell it that a particular element on the old page is the same thing as an element on the new page, and it’ll morph one into the other.

They used to only work in single-page apps (where JavaScript handles the navigation). Now they work between normal, separate HTML pages too - which is perfect for a static site like this one.

1. Turn them on

This is the bit that surprised me. To enable them across your whole site, you just add this to your CSS:

@view-transition {
  navigation: auto;
}

That’s it. Every same-site navigation now gets a soft cross-fade instead of a hard cut. Browsers that don’t support it just ignore it and navigate as normal - so there’s no downside.

2. Give the box a name

To get the box to slide, the browser needs to know that the box on the old page and the box on the new page are the same element. You do that with view-transition-name:

.nav-active-box {
  view-transition-name: nav-active-box;
}

The name can be anything, as long as it’s unique on the page. Now when you go from /work to /blog, the browser sees a nav-active-box in both places, works out where each one is, and animates the position and size between them.

One small catch - originally the box was just a border on the link itself. That meant the whole link (text and all) was getting morphed. So I pulled the box out into its own little <span> that sits on top of the active link. That way only the box moves, and the text stays put.

3. Keep it crisp

By default the browser animates a picture of the element - which means when the box stretches between a short word like “work” and a longer one like “projects”, the 1px border gets stretched too and looks a bit blurry.

The fix was to hide the snapshots and draw the border on the transition itself instead:

::view-transition-group(nav-active-box) {
  border: 1px solid currentColor;
  animation-duration: 200ms;
}

::view-transition-old(nav-active-box),
::view-transition-new(nav-active-box) {
  display: none;
}

Now the border is a real border the whole way through - so it stays sharp no matter how much it changes size.

4. Tidy up the edges

A few little things that made a big difference:

  • Stop the nav cross-fading. The nav is identical on every page, so I gave it its own name and turned its animation off - otherwise it flickers slightly.
  • Stop the page shifting. Some pages are long enough to have a scrollbar and some aren’t, which nudges everything sideways. scrollbar-gutter: stable on the html element fixes that.
  • The home page. There’s no box on the home page, so there’s nowhere for it to slide to (or from). In that case the box just fades in or out instead - this needed a tiny bit of JavaScript to spot when it’s happening.
  • Respect reduced motion. If someone has reduced motion turned on in their settings, all of the animations are switched off.
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

That’s it.

A couple of lines to switch it on, a name for the thing you want to move, and a bit of polish. No framework, no animation library - the browser does the heavy lifting.

If you’ve got a static site, it’s well worth a go. Start with the @view-transition rule on its own and see how it feels - you might be surprised how much nicer it makes things.