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-galleryhelps contain the below elements.#gridcontains the 4 colored quadrants.#zoomis 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:
widthandheighton#griduse the samevar(--size)value (200px in this case) to keep things square in its overall sizegrid-template-columnsandgrid-template-rowseach use 2autovalues to ensure thedivelements 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-transitionis 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-transitionproperty/value from the#zoomelement. - Think of the 'new' in the pseudo-selector
::view-transition-new(zoom-transition)as what happens when the#zoomelement changes fromdisplay:nonetodisplay:block. When this transition happens fromnonetoblockCSS selects applies thezoom-inanimation which is now connected to the#zoomelement. - The opposite of this is the 'old' in the psuedo-selector
::view-transition-old(zoom-transition)which is going to select the#zoomelement again and kick off thezoom-outanimation as we go from adisplay:blocktodisplay: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
clicklistener on the#gridelement that will grab the#zoomelement 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
#zoomelement
- Make the element visible with
- Note that
transitionstores thedocument.startViewTransitionfunction which immediately fires its callback that modifies the DOM. Thistransitionisawait-ed so that we can let the animation complete without interruption. - We do have click functionality on the
#zoomelement as well which modifies itsdisplayvalue tononeand 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