Animating your SPA with the new View Transitions API
As of this month, View Transitions API is stable in Chrome and Edge, giving web developers an easy and lightweight way of animating page transitions in Single Page Applications (SPAs).
Mobile apps have been using animated transitions like this for years, not only improving the visual experience but also helping users stay in context and not be confused about a sudden page transition.
We've enabled View Transitions in our app. As you can see below, the top and left bar stays in their place, while the page content fades and slides in:
If you already have an SPA, it's incredibly simple to implement, but as with any new technology, there are caveats to be aware of. In this blog I'll explain how we implemented View Transitions.
Before View Transitions API
Before View Transitions API it was cumbersome and challenging to implement animated page transitions. Figuring out the position of the elements of the previous page vs. the new isn't trivial, especially when also dealing with scroll, back/forward navigation, and making everything degrade gracefully if browsers don't support it. JavaScript libraries that make this somewhat easier - like Highway JS do exist - but it looks like they are no longer needed.
Getting started with the first transition - the fade
const spaNavigate = content => {
// Fallback for browsers that don't support View Transitions API:
if (!document.startViewTransition) {
updateDOM(content);
return;
}
document.startViewTransition(_ => updateDOM(content));
}
This is enough to get you started with the default transition: cross-fade.
Let's see how that looks:
Customizing the animation and naming areas
This was actually a great first take. But our goal was to make the page content slide in from the left. This CSS is the same that Material Design 's Shared Axis is using:
@keyframes fade-in { from { opacity: 0; }}
@keyframes fade-out { to { opacity: 0; }}
@keyframes slide-from-left { from { transform: translateX(-30px); }}
@keyframes slide-to-right { to { transform: translateX(30px); }}
::view-transition-old(root) {
animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-right;
}
::view-transition-new(root) {
animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-left;
}
This is how things looked after adding that CSS:
Ok, it's clear that the left and top bar have to be fixed somehow. The solution is to give those sections a view-transition-name
and then not attaching any of the view-transition pseudo classes to them:
.topbar {
...
view-transition-name: topbar;
}
.sidebar {
...
view-transition-name: sidebar;
}
That's it. The transition now looks great:
Animations cause flicker
One issue that we faced was that CSS animations would cause the view transitions to flicker and ruin the whole experience. You can see how it looked here:
After investigating this for a while, we came up with a solution that simply pauses all CSS animations during the view transition.
You need to await
the finished
promise on the object returned by the document.startViewTransition
call, so remember the async
keyword on your method:
const spaNavigate = async (content) => {
// Fallback for browsers that don't support View Transitions API:
if (!document.startViewTransition) {
updateDOM(content);
return;
}
// Pause CSS animations
const stylesheet = document.styleSheets[0];
const cssRule = stylesheet.insertRule("*, *::after { animation-play-state: paused!important }");
const viewTransition = document.startViewTransition(_ => updateDOM(content));
try {
await viewTransition.finished;
} finally {
// Unpause CSS animations
document.styleSheets[0].deleteRule(0);
}
}
Alright, let's see how that looks:
Somewhat more complex code, but stopping the animations was enough to ensure silky smooth transitions on pages with CSS animations. Nota that if your pages have complex CSS transitions, you may encounter some of the same problems.
Next step: non-SPAs
The Chrome team is currently working on bringing View Transitions API to regular web pages where each navigation is a full page reload.
If you enable chrome://flags/#document-transition
you'll be able to see an early preview of how this will work. We tried implementing page transitions on this very website (go try for yourself), but from what we can see, it's still very early days. Some transitions work, some change behavior on each navigation, some cause flicker. But overall we believe this is a very interesting feature which could mean less dependence on large JS frameworks like Angular and React.