View Transitions!
informatives by Bast on Wednesday February 19th, 2025
One of the things I've wanted to do forever is to implement what's called view transitions. In the more general sense, this would be some sort of animation to fade between the different pages on my blog. They all have the same outer style, use the same navbar and framework, etc. They also load extremely fast (due to plenty of time and effort and interest on my part), and thus I've always thought my blog would be a great testing piece or base for some sort of cross-page animation.
The problem, then, was I don't particularly enjoy javascript animations. SPAs, specifically, as well. I have had so many poor experiences with them that I have no desire to re-inflict them on my visitors. Nor do I have any desire to plumb the depths of NPM, or attempt to rewrite browser navigation in any way that I don't have to.
So, I went without. Which was fine. I was still working on basic structure and organization anyway. I had many things to do. But today was the day I took the plunge.
The documentation is still relatively sparse, which will probably improve with time. But I was able to tease out the important parts. First, the way the animations function is by constructing a set of overlaying pseudo-elements outside the page context, and animating one out, then the other in.
These elements are ::view-transition
, which contains all of the rest, ::view-transition-group(identifier)
for (and this isn't clear in the docs) each named element found in both before and after navigational pages, and :view-transition-old(identifier)
/:view-transition-new(identifier)
. The old animation fires first, then the new animation fires afterwards.
My truly desired animation involves height
, but I don't think I'll be able to animate height: auto
for some time yet, unfortunately, so the crossfade default is good enough for today.
You opt into view-transitions via the css rule:
@view-transition {
navigation: auto;
}
It's a magic rule, so not all of it means particularly anything. navigation
is a real css property, though. It can be set to auto
or none
. None meaning, don't transition this element. Auto meaning transition. I expect sometime in the far future this will be customizable to navigation types like traverse/push/replace but that's not available today.
You shouldn't copy-paste that directly though. It's nestable inside other declarations, and I suggest you do so with prefers-reduced-motion:
@media not (prefers-reduced-motion: reduce) {
@view-transition {
navigation: auto;
}
}
This is the exact code I use, plus some more detail for my theme in particular. This will enable view transitions by default and disables them if less motion is desired.
Now, with just this you have view transitions. You're done. When you navigate to another page via automated or manual means (although the refresh button does not trigger these, explicitly so), you will get the default crossfade transition. Your browser (user-agent) comes with the following style by default:
:root {
view-transition-name: root;
}
Which causes this. What does this do?
This code registers a "transition context" named root
, using the root element, :root
. By default, :view-transition-old
and -new
fade out (opacity 1 to 0) and in (opacity 0 to 1). So, you navigate to a new page, and the entire page crossfades across.
But most people don't want this. They want a custom animation (you set animation:
on the -old
and -new
selectors) or they only want to fade one item.
You can do that. You can even customize the animations of different items. You do this by given each of said items a view-transition-name
. Then you can use said name in :view-transition-new(unique-here)
to specify it's interaction. Or you can use none
to disable it for an element. That's probably best used to turn one off if you have specific styling guides.. or, in my case, to disable it on the root element. Opacity fading doesn't work exactly as you would expect, and so I prefer the instant-swap for everything not in the main content zone for my blog, and crossfade for the content.
That's mostly it. There's a JS api (which I'm not using). There's some new page load and store events to catch your stuff when the user navigates away. But since this feature is only available in chrome, generally, you should follow best practice and be using progressive enhancement. Which the CSS functions beautifully as. In firefox, nothing happens, but also, nothing goes wrong.
Don't forget to support the free web. It got us where we are today.