Have you ever stumbled upon something new and went to research it just to find that there is little-to-no information about it? It’s a mixed feeling: confusing and discouraging because there is no apparent direction, but also exciting because it’s probably new to lots of people, not just you. Something like that happened to me while writing an Almanac’s entry for the @view-transition
at-rule and its types
descriptor.
You may already know about Cross-Document View Transitions: With a few lines of CSS, they allow for transitions between two pages, something that in the past required a single-app framework with a side of animation library. In other words, lots of JavaScript.
To start a transition between two pages, we have to set the @view-transition
at-rule’s navigation
descriptor to auto
on both pages, and that gives us a smooth cross-fade transition between the two pages. So, as the old page fades out, the new page fades in.
@view-transition {
navigation: auto;
}
That’s it! And navigation
is the only descriptor we need. In fact, it’s the only descriptor available for the @view-transition
at-rule, right? Well, turns out there is another descriptor, a lesser-known brother, and one that probably envies how much attention navigation
gets: the types
descriptor.
types
?
What do people say about Cross-Documents View Transitions are still fresh from the oven, so it’s normal that people haven’t fully dissected every aspect of them, especially since they introduce a lot of new stuff: a new at-rule, a couple of new properties and tons of pseudo-elements and pseudo-classes. However, it still surprises me the little mention of types
. Some documentation fails to even name it among the valid @view-transition
descriptors. Luckily, though, the CSS specification does offer a little clarification about it:
The
types
descriptor sets the active types for the transition when capturing or performing the transition.
To be more precise, types
can take a space-separated list with the names of the active types (as <custom-ident>
), or none
if there aren’t valid active types for that page.
- Name: types
- For:
@view-transition
- Value:
none | <custom-ident>+
- Initial: none
So the following values would work inside types
:
@view-transition {
navigation: auto;
types: bounce;
}
/* or a list */
@view-transition {
navigation: auto;
types: bounce fade rotate;
}
Yes, but what exactly are “active” types? That word “active” seems to be doing a lot of heavy lifting in the CSS specification’s definition and I want to unpack that to better understand what it means.
Active types in view transitions
The problem: A cross-fade animation for every page is good, but a common thing we need to do is change the transition depending on the pages we are navigating between. For example, on paginated content, we could slide the content to the right when navigating forward and to the left when navigating backward. In a social media app, clicking a user’s profile picture could persist the picture throughout the transition. All this would mean defining several transitions in our CSS, but doing so would make them conflict with each other in one big slop. What we need is a way to define several transitions, but only pick one depending on how the user navigates the page.
The solution: Active types define which transition gets used and which elements should be included in it. In CSS, they are used through :active-view-transition-type()
, a pseudo-class that matches an element if it has a specific active type. Going back to our last example, we defined the document’s active type as bounce
. We could enclose that bounce animation behind an :active-view-transition-type(bounce)
, such that it only triggers on that page.
/* This one will be used! */
html:active-view-transition-type(bounce) {
&::view-transition-old(page) {
/* Custom Animation */
}
&::view-transition-new(page) {
/* Custom Animation */
}
}
This prevents other view transitions from running if they don’t match any active type:
/* This one won't be used! */
html:active-view-transition-type(slide) {
&::view-transition-old(page) {
/* Custom Animation */
}
&::view-transition-new(page) {
/* Custom Animation */
}
}
I asked myself whether this triggers the transition when going to the page, when out of the page, or in both instances. Turns out it only limits the transition when going to the page, so the last bounce animation is only triggered when navigating toward a page with a bounce
value on its types
descriptor, but not when leaving that page. This allows for custom transitions depending on which page we are going to.
The following demo has two pages that share a stylesheet with the bounce
and slide
view transitions, both respectively enclosed behind an :active-view-transition-type(bounce)
and :active-view-transition-type(slide)
like the last example. We can control which page uses which view transition through the types
descriptor.
The first page uses the bounce
animation:
@view-transition {
navigation: auto;
types: bounce;
}
The second page uses the slide
animation:
@view-transition {
navigation: auto;
types: slide;
}
You can visit the demo here and see the full code over at GitHub.
types
descriptor is used more in JavaScript
The The main problem is that we can only control the transition depending on the page we’re navigating to, which puts a major cap on how much we can customize our transitions. For instance, the pagination and social media examples we looked at aren’t possible just using CSS, since we need to know where the user is coming from. Luckily, using the types
descriptor is just one of three ways that active types can be populated. Per spec, they can be:
- Passed as part of the arguments to
startViewTransition(callbackOptions)
- Mutated at any time, using the transition’s types
- Declared for a cross-document view transition, using the
types
descriptor.
The first option is when starting a view transition from JavaScript, but we want to trigger them when the user navigates to the page by themselves (like when clicking a link). The third option is using the types
descriptor which we already covered. The second option is the right one for this case! Why? It lets us set the active transition type on demand, and we can perform that change just before the transition happens using the pagereveal
event. That means we can get the user’s start and end page from JavaScript and then set the correct active type for that case.
I must admit, I am not the most experienced guy to talk about this option, so once I demo the heck out of different transitions with active types I’ll come back with my findings! In the meantime, I encourage you to read about active types here if you are like me and want more on view transitions: