Table of contents
Despite the availability of existing animation APIs like CSS transitions, CSS animations, and the Web Animation API, navigating between views on the web can still feel abrupt and jarring, as users switch between different documents or views.
How to enable the View Transitions API?
The View transition API should be on by default for Chrome 111 beta users.
Or by enabling chrome://flags/#view-transition flag in Chrome 109+.
The same for Chromium based browsers (Microsoft Edge, Brave, Opera …)
Mozilla Firefox ( Not implemented yet)
Why do we need another animation API?
Before the View Transitions API, there was nothing developers could do about navigating between views or documents without switching to an SPA model.
View Transitions API provides a way to create an animated transition between two documents, without creating an overlap in the transition.
What are we building?
Live Demo:
https://codesandbox.io/p/sandbox/view-transition-api-qgt1cy
Let’s code
We will build a simple Transitions Animation using the new View Animations API, and explore different options of this new JavaScript API.
function animatedTransition() {
if (document.startViewTransition) {
return "Yesss, your browser support View Transitions API";
}
return "Oops, unfortunately your browser don't support View Transitions API";
}
...
document.getElementById("support").innerHTML = animatedTransition();
→ In this JavaScript Function, if the browser supports View Transitions API (document.startViewTransition).
It will return a string “Yes, your browser support View Transitions API”.
Else it will return “Oops, unfortunately your browser doesn't support View Transitions API”.
→ Then we show the result in the inner html of a div with an id “support” in our html files.
function navigateTo(path) {
if (!document.startViewTransition) {
window.location.href = path;
return;
}
document.startViewTransition(() => {
window.location.href = path;
});
}
...
const btn = document.getElementById("btn");
btn.addEventListener("click", function (e) {
navigateTo(this.dataset.target);
});
→ In this function (navigateTo) we accept a parameter “path” that includes the path to the new page, and navigate to it using the location.href method from the window JavaScript object.
If our browser does not support View Transitions API, we pass directly the path to our location.href.
And if it support it, we pass our path to location.href inside a call-back in the startViewTransition() function:
document.startViewTransition(() => {
window.location.href = path;
});
Then we add an eventListener “click” to all buttons with the id: “btn”, and we will extract the dataset with the name of “target” and pass the result to our navigateTo() function.
When .startViewTransition() is called:
→ The current state captured including a screenshot of the page.
→ the API constructs a pseudo-element tree:
view-transition
└─ ::view-transition-group(root)
└─ ::view-transition-image-pair(root)
├─ ::view-transition-old(root)
└─ ::view-transition-new(root)
→ Then the call-back function is called: ”() => {window.location.href = path}”.
→ Capture the new state of our page.
→ Reconstruction of the DOM to include the new elements.
→ view-transition: sits on top of the animation as an overlay.
→ view-transition-group: Responsible for animating the size and position between the old and the new stats.
→ view-transition-image-pair: hold the current state of the animation.
→ The animation transits from the starting view (view-transition-image-old) to the new view (view-transition-image-new).
→ By default The animation is a cross-fade transition.
→ The animation is done with CSS animations, both stats render as CSS ( the new CSS replaces the old one), From opacity:1 to opacity:0 for the old view, then from opacity:0 to opacity:1 for the new view.
→ The root key means that the animation i'll be applied to the entire page.
We can easily customise with CSS:
→ Changing default animation timing, the outgoing View go out fast and the incoming View come in slow
::view-transition-old(root){
animation-duration: 0.5s;
}
::view-transition-new(root) {
animation-duration: 10s;
}
→ Changing the default animation
@keyframes fade-and-scale-in {
from {
opacity: 0;
transform: scale(0);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes fade-and-scale-out {
from {
opacity: 1;
transform: scale(1);
}
to {
opacity: 0;
transform: scale(0);
}
}
→ Now grouping all these properties using CSS Shorthand
/* Views Animation */
::view-transition-old(root) {
animation: fade-and-scale-out 0.5s ease-in-out 1 forwards;
}
::view-transition-new(root) {
animation: fade-and-scale-in 10s ease-in-out 1 forwards;
}
@keyframes fade-and-scale-in {
from {
opacity: 0;
transform: scale(0);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes fade-and-scale-out {
from {
opacity: 1;
transform: scale(1);
}
to {
opacity: 0;
transform: scale(0);
}
}
I will explain this line
{
animation: fade-and-scale-out 1s ease-in-out 1 forwards;
}
.fade-and-scale-out: This is the name of the animation. It's also used as the class name for the element that will be animated.
1s: This is the duration of the animation, which is set to 1 second.
ease-in-out: This is the timing function of the animation. In this case, it starts slowly, accelerates through the middle, and slows down at the end.
1: This is the number of times the animation will repeat. In this case, it will only run once.
forwards: This is the value of the animation-fill-mode property, which determines what styles are applied to the element before and after the animation runs. In this case, forwards means that the element will retain the styles from the last keyframe of the animation after the animation ends.
And for this line
{
animation: fade-and-scale-in 1s ease-in-out 1 forwards;
}
element that will be animated.
.fade-and-scale-in: This is the name of the animation. It's also used as the class name for the element that will be animated.
1s: This is the duration of the animation, which is set to 1 second.
ease-in-out: This is the timing function of the animation. In this case, it starts slowly, accelerates through the middle, and slows down at the end.
1: This is the number of times the animation will repeat. In this case, it will only run once.
forwards: This is the value of the animation-fill-mode property, which determines what styles are applied to the element before and after the animation runs. In this case, forwards means that the element will retain the styles from the last keyframe of the animation after the animation ends.
Let’s see the code in action:
The complete styles.css source code
body {
font-family: sans-serif;
}
.card {
display: flex;
height: 100vh;
justify-content: center;
align-items: center;
flex-direction: column;
}
.card > h1 {
font-family: Cambria, Cochin, Georgia, Times, "Times New Roman", serif;
font-style: italic;
font-size: 2rem;
}
.card > button {
font-family: Cambria, Cochin, Georgia, Times, "Times New Roman", serif;
font-size: 1.5rem;
padding: 10px;
}
.bgHome {
background-color: #f9f54b;
}
.bgAbout {
background-color: #8bf5fa;
}
.support {
margin-top: 100px;
}
/* Views Animation */
::view-transition-old(root) {
animation: fade-and-scale-out 0.5s ease-in-out 1 forwards;
}
::view-transition-new(root) {
animation: fade-and-scale-in 10s ease-in-out 1 forwards;
}
@keyframes fade-and-scale-in {
from {
opacity: 0;
transform: scale(0);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes fade-and-scale-out {
from {
opacity: 1;
transform: scale(1);
}
to {
opacity: 0;
transform: scale(0);
}
}
The complete “index.js” source code
function animatedTransition() {
if (document.startViewTransition) {
return "Yesss, your browser support View Transition API";
}
return "Oops, unfortunately your browser don't support View Transition API";
}
function navigateTo(path) {
if (!document.startViewTransition) {
window.location.href = path;
return;
}
document.startViewTransition(() => {
window.location.href = path;
});
}
document.getElementById("support").innerHTML = animatedTransition();
const btn = document.getElementById("btn");
btn.addEventListener("click", function (e) {
navigateTo(this.dataset.target);
});
We have also two html files “index.html” and “about.html”:
- index.html
<!DOCTYPE html>
<html>
<head>
<title>View Transition API</title>
<meta charset="UTF-8" />
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<div class="card bgHome">
<h1>welcome To Home</h1>
<button id="btn" data-target="about.html">About page</button>
<div id="support" class="support"></div>
</div>
<script src="index.js"></script>
</body>
</html>
As you can see, we passed “about.html” to our data-target
<button id="btn" data-target="about.html">About page</button>
- about.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>About</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<div class="card bgAbout">
<h1>welcome To About</h1>
<button id="btn" data-target="index.html">Home page</button>
<div id="support" class="support"></div>
</div>
<script src="index.js"></script>
</body>
</html>
Now we passed “index.html” to our data-target in our button.
Full complete source code
https://codesandbox.io/p/sandbox/view-transition-api-qgt1cy
Conclusion
JavaScript’s continuously evolving language and APIs, enable developers to create more powerful and efficient applications, with the ability to leverage modern hardware and incorporate machine learning and AI. JavaScript is here to stay and offers an ever-increasing range of tools and resources for developers.