How to create beautiful view transitions in Nuxt using the new View Transitions API
Intro
The Web platform is thriving with all those new APIs that were introduced lately. Both JS and CSS are developing at a fantastic pace and allow us to achieve certain effects much easier and more pleasantly than we have done so far. One of the most interesting APIs that have recently gained wider support is the View Transitions API.
What is Web Transitions API?
Web Transitions API is a new API that gives us a better way of creating animated transitions between different DOM states while also updating the DOM contents in a single step. Doing it manually in SPA required writing a lot of CSS and JS and required all HTML content from two pages at the moment of the transition. This caused some issues and was not a perfect solution in terms of accessibility. With the new API, it's much easier to create stunning mobile-like transitions between app states which are more accessible and performant. Currently, it works for Chromium-based browsers and this is how view-transition-name which is all you need is supported now:
Of course, this feature has to be supported by your favorite framework. We are lucky because Nuxt was the first framework that gave us the ability to try this new and amazing feature! Please take into account that this is still an experimental feature.
Create stunning view transition in Nuxt
First of all, please check the live demo on my very own website. For now, you have to use Chrome to notice the effects.
When you click one of the articles on the homepage, you can observe a really cool animation of some elements inside the article card. Here is low-res GIF, but it's better to see it live to better understand all interactions:
As you can see, that are 4 elements involved in the transition:
- article cover image
- article title
- author
- date
Every single element mentioned above is making its own transition from the position on the homepage to the position on the article page.
Let's check how we can achieve it with Nuxt!
Enabling View Transitions in Nuxt
To use View Transitions in Nuxt you have to enable it in nuxt.config file:
// nuxt.config.ts
export default defineNuxtConfig({
// ...
experimental: {
viewTransition: true,
// ...
},
// ...
});
Adding view transitions in (S)CSS
Now we have to make a connection between the same elements on pages we want to make a transition by setting the matching view transition names. I want to make a transition whenever a user will click on one of the article cards:
If our article card is wrapped in article tag and we would like to create a view transition name for the image cover inside our card we can do it that way in SCSS:
article {
img {
view-transition-name: article-thumb;
}
}
Ok, so we informed CSS that we want to apply view transition for the article image and named it accordingly. Now we have to find the article image on the article page itself.
Let's do the same thing for that element. Since I have it in a separate component with scoped styles and we have to do is:
img {
view-transition-name: article-thumb;
}
Now, let's do the same for the article title, date and author:
CSS in homepage ArticleCard component:
article {
img {
view-transition-name: article-thumb;
}
.article-title {
view-transition-name: article-title;
}
.author-info {
view-transition-name: author;
}
.date {
view-transition-name: date;
}
}
CSS in article ArticleHeader component.
img {
view-transition-name: article-thumb;
}
.article-title {
view-transition-name: article-title;
}
.date {
view-transition-name: date;
}
.article-thumb {
border-radius: 20px;
max-width: 100%;
}
How does it work? By matching view-transition-name. During navigation, it checks if there are view-transition-names with the same name on both pages and applies view-transition at the exact time of route change. But wait! How this would work if we have let's say 20 articles on the homepage? It means that we have 20 elements with the same page-transition-name right? It won't work that way, because the browser will not know which element to use in the transition. We need to somehow inform the browser that we only want to involve clicked article in the transition. There are many ways to do it but I like to apply the .active class on the clicked article card and set the view transition only for article cards with .active class.
The CSS for the homepage would look like that:
article {
&.active {
img {
view-transition-name: article-thumb;
}
.article-title {
view-transition-name: article-title;
}
.author-info {
view-transition-name: author;
}
.date {
view-transition-name: date;
}
}
}
We don't have to do the same thing for ArticleHeader because when you will navigate to a single article there is only one title, cover image, date and author. So you only have to do it if you potentially have many elements with the same view-transition-name from which you have to make a transition.
Now we have to programmatically add those classes to clicked Article Card.
Active article logic
On my blog, I'm using Pinia for state management so we can use it for our active article logic. Let's create a new store for articles:
import { defineStore } from 'pinia';
// stores/article.ts
export const useArticlesStore = defineStore('articles', () => {
const activeArticle = ref(null);
function setActiveArticle (article) {
activeArticle.value = article;
}
return { activeArticle, setActiveArticle };
});
This store is really simple. It stores the whole reference to the active article and has a setter for setting the active article.
Now in our ArticleCard component, we have to import the store:
// ArticleCard.vue
import { useArticlesStore } from '@/stores/articles';
const articlesStore = useArticlesStore();
On every click on every link in the ArticleCard, we have to set the clicked article as active. We can do it that way for clickable article thumb:
<!-- ArticleCard.vue -->
<NuxtLink class="article-thumb-wrapper" :to="article._path" @click="articlesStore.setActiveArticle(article)">
<div class="article-thumb">
<nuxt-img :src="article.thumb" />
</div>
</NuxtLink>
Now, when a specific article card will be clicked, this article will be marked as active inside our articles store. All we need to make to finish the work on this is to add the active class on the active article card wrapping element:
<article class="article-item" :class="{active: articlesStore.activeArticle && article._id === articlesStore.activeArticle._id}">
It conditionally applies the active class when the id of the article in ArticleCard is matching the ID of the active article in the articles store.
More context:
<article class="article-item" :class="{active: articlesStore.activeArticle && article._id === articlesStore.activeArticle._id}">
<NuxtLink class="article-thumb-wrapper" :to="article._path" @click="articlesStore.setActiveArticle(article)">
<div class="article-thumb">
<nuxt-img :src="article.thumb" />
</div>
</NuxtLink>
<!-- other elements -->
</article>
And now it should work!
Summary
View Transitions API is fantastic addition to freshly introduced web APIs. In this article, I showed the simplest solution to create visually stunning. You can customize view-transitions and have more fine-grained control by JavaScript. I encourage you to review the documentation to learn more. What's cool is that even though not all browsers support it, using it now won't break anything in browsers that don't support it. And when they start, it will start working! I'm pretty sure that in the nearest future, many browsers will implement view transitions because it can improve user experience and create mobile-like, native feeling when using web browsers.