View Transitions API
2025.07.04#learning-lessonsThe View Transition API in CSS is relatively new and is a great tool for animating elements that trigger immediate visibilty changes from block
to none
and vice-versa. Click on the quadrants below to see an example:
Each quadrant triggers an animation to display a scaled up version of the quadrant over the top of the grid. This is akin to an image gallery but simplified into this Micrsoft-Simon-esque looking object. Full demo on Codepen
HTML Setup
<div id="view-transition-gallery">
<main id="grid">
<div></div>
<div></div>
<div></div>
<div></div>
</main>
<main id="zoom">
</main>
</div>
🔑 points:
#view-transition-gallery
helps contain the below elements.#grid
contains the 4 colored quadrants.#zoom
is the 'scaled up' gallery item appearing after clicking a quadrant color.
The #grid quadrants
#grid {
display: grid;
width: var(--size);
height: var(--size);
grid-template-columns: auto auto;
grid-template-rows: auto auto;
gap: 10px;
}
#grid div:nth-child(1) {
background-color: #ffd1dc;
}
...
/* :nth-child() background-color definition for every other div */
/* ...more styles for box shadows and hovering */
🔑 points:
The goal is to set up a 2x2 grid with the following:
width
andheight
on#grid
use the samevar(--size)
value (200px in this case) to keep things square in its overall sizegrid-template-columns
andgrid-template-rows
each use 2auto
values to ensure thediv
elements orient themselves as a 2x2 grid.- Coloring each quadrant is a result of the
:nth-child(<number>)
rule that continues for eachdiv
The #zoom element
#zoom {
display: none;
height: calc(var(--size) * 0.8);
width: calc(var(--size) * 0.8);
margin: calc(var(--size) * 0.1);
view-transition-name: zoom-transition;
box-shadow: 10px 10px 20px rgba(0, 0, 0, 0.2);
}
- This element is what gets scaled up (acting like an image gallery element). Note that it starts with
display:none
- It doesn't completely cover the 4 quadrant squares when scaled up due to the
calc(...)
statements. This lets you click on the outer edges of the visible colored quadrants to bring in a different zoomed quadrant. view-transition-name: zoom-transition
is probably the most important piece of this. It establishes a selector for CSS to look for when deciding what elements to take "snapshots" of to prepare for the before and after states as animations happen.
View Transition (Where the magic 🪄 happens)
::view-transition-new(zoom-transition)
{ animation: zoom-in 0.5s ease-in-out; }
::view-transition-old(zoom-transition)
{ animation: zoom-out 0.5s ease-out; }
@keyframes zoom-in {
from { transform: scale(0); }
to { transform: scale(1); }
}
@keyframes zoom-out {
from { transform: scale(1); }
to { transform: scale(0); }
}
🔑 points:
- Both
::view-transtion-new(zoom-transition)
and::view-transition-old(zoom-transition)
are selectors tied to theview-transition-name: zoom-transition
property/value from the#zoom
element. - Think of the 'new' in the pseudo-selector
::view-transition-new(zoom-transition)
as what happens when the#zoom
element changes fromdisplay:none
todisplay:block
. When this transition happens fromnone
toblock
CSS selects applies thezoom-in
animation which is now connected to the#zoom
element. - The opposite of this is the 'old' in the psuedo-selector
::view-transition-old(zoom-transition)
which is going to select the#zoom
element again and kick off thezoom-out
animation as we go from adisplay:block
todisplay:none
A dash of Javascript
We do need a bit of Javascript to change the display
state of the #zoom
element from none
to block
(and vice-versa), which is done with this Javascript:
document.getElementById("grid").addEventListener('click', async (el) => {
const transition = document.startViewTransition(() => {
if (el) {
const color = getComputedStyle(el.target).backgroundColor;
const zoomEl = document.getElementById("zoom");
zoomEl.style.display = "block";
zoomEl.style.backgroundColor = color;
}
})
await transition.ready;
})
document.getElementById("zoom").addEventListener('click', async (e) => {
const transition = document.startViewTransition(() => {
e.target.style.display = "none";
});
await transition.ready;
});
🔑 points:
- We add a
click
listener on the#grid
element that will grab the#zoom
element and do 2 things:- Make the element visible with
zoomEl.style.display="block"
- Also take the color from the quadrant that was clicked and apply it to the
#zoom
element
- Make the element visible with
- Note that
transition
stores thedocument.startViewTransition
function which immediately fires its callback that modifies the DOM. Thistransition
isawait
-ed so that we can let the animation complete without interruption. - We do have click functionality on the
#zoom
element as well which modifies itsdisplay
value tonone
and also awaits the transition.
Without the transition
For comparison sake, lets look at what the experience would be like without the transition. Which do you like better ? 😁
~ Zano