View Transitions Applied: Smoothly animating a border-radius with a View Transition

Because View Transitions animate snapshots, you can morph any element into another element. But for some type of transitions, the use of snapshots can sometimes work against you. For example: when the border-radius changes between the old and the new snapshot, you most likely want the border-radius to nicely animate from the old to the new state instead of seeing two snapshots fade.

~

🌟 This post is about View Transitions. If you are not familiar with the basics of it, check out this 30-min talk of mine to get up to speed.

~

# The Problem

The problem can be seen in the following demo that morphs a .card using a View Transition. The .card has a view-transition-name and animates its border-radius from 0.25rem to 3rem and back, amongst a few other properties that change along with it (such as the font-size and aspect-ratio).

See the Pen
View Transitions with a Border Radius (1/3 – Problem)
by Bramus (@bramus)
on CodePen.

Pay close attention the corners of the .card element as it transitions: because View Transitions fade snapshots, the border-radius does not nicely animate but simply fades from the old state to the new state.

Note that the text inside the .card also has a view-transition-name set to it, so that it gets captured separately from the .card itself. This will turn out to be key later on.

~

# The (partial) Solution

The solution to this problem is to manipulate the ::view-transition-group which contains the snapshots. The thing you need to do is add an extra animation the ::view-transition-group which performs the smooth animation you want. In my case, that is an animation of the border-radius. Don’t forget to set the overflow to clip to make sure the snapshots don’t bleed out.

@keyframes adjust-border-radius {
	from {
		border-radius: 0.25rem;
	}
	to {
		border-radius: 3rem;
	}
}

::view-transition-group(card) {
	animation-name: -ua-view-transition-group-anim-card, adjust-border-radius;
	overflow: clip;
	background: #ccc;
}

:active-view-transition-type(shrink)::view-transition-group(card) {
	animation-direction: normal, reverse;
}

The adjust-border-radius animation – which animates the border-radius – gets added to the existing -ua-view-transition-group-anim-card animation. The newly added animation gets reversed when the card shrinks, which is communicated from JS to CSS using View Transition Types.

See the Pen
View Transitions with a Border Radius (2/3 – Workaround)
by Bramus (@bramus)
on CodePen.

~

# Dealing with changing backgrounds

In the previous demo I cheated a bit by duplicating the background-color onto the group, this to cover up some inaccuracies. You can see these inaccuracies more clearly when the background-color of the .card also changes. Pay close attention to the corners of the following demo: you can see it’s not 100% perfect.

See the Pen
View Transitions with a Border Radius (2/3 – Workaround, with random text)
by Bramus (@bramus)
on CodePen.

(While at it, I updated the code to also change the text inside the card).

The solution here too is to move more properties that get animated onto the ::view-transition-group. In this case, it’s the background-color.

To actually see the background animating as part of a transition, the snapshots that make up the card (but not its contents) need to be hidden while the transition runs. This can be done by setting the ::view-transition-image-pair(card) to display: none;. This can safely be done because of the fact that the text content of the card gets captured separately.

@keyframes adjust-group {
	from {
		background: #ccc;
		border-radius: 0.25rem;
	}
	to {
		background: lightblue;
		border-radius: 3rem;
	}
}
::view-transition-group(card) {
	animation-name: -ua-view-transition-group-anim-card, adjust-group;
}
:active-view-transition-type(shrink)::view-transition-group(card) {
	animation-direction: normal, reverse;
}
::view-transition-image-pair(card) {
	display: none;
}

See the Pen
View Transitions with a Border Radius (2/3 – Workaround, with random text + background animation –fixed)
by Bramus (@bramus)
on CodePen.

If the snapshot with the foreground stuff of the element bleeds out of the snapshot with the background stuff, you also need to duplicate the border-radius and clip onto that snapshot’s ::view-transition-group.

In the future Nested View Transition Groups will solve this. This feature is currently getting implemented in Chrome.

~

# Dealing with changing borders

A tricky thing to incorporate in the solution is animating borders. The trickiness comes from the fact that the snapshots are taken using the border-box. This means that when duplicating a border onto the ::view-transition-group you need to make sure its box-sizing is set to border-box, regardless of the box-sizing of the snapshotted element.

With this set, you can safely duplicate the border onto the ::view-transition-group and then animate it as part of its keyframes.

@keyframes adjust-group {
	from {
		border-radius: 0.25rem;
		background: #ccc;
		border-width: 2px;
	}
	to {
		border-radius: 3rem;
		background: lightblue;
		border-width: 8px;
	}
}
::view-transition-group(card) {
  box-sizing: border-box;
	border: 2px solid black;
	animation-name: -ua-view-transition-group-anim-card, adjust-group;
}
:active-view-transition-type(shrink)::view-transition-group(card) {
	animation-direction: normal, reverse;
}
::view-transition-image-pair(card) {
	display: none;
}

See the Pen
View Transitions with a Border Radius (2/3 – Workaround, with random text + border)
by Bramus (@bramus)
on CodePen.

~

# In Summary

To smoothly animate things like borders as part of a View Transition, you need to duplicate that animation onto the ::view-transition-group. For best effect, have the View Transition separately capture the background and foreground of the element you’re animating. This can be done by giving each a view-transition-name.

~

# Spread the word

Feel free to reshare one of the following posts on social media to help spread the word:

~

Published by Bramus!

Bramus is a frontend web developer from Belgium, working as a Chrome Developer Relations Engineer at Google. From the moment he discovered view-source at the age of 14 (way back in 1997), he fell in love with the web and has been tinkering with it ever since (more …)

Unless noted otherwise, the contents of this post are licensed under the Creative Commons Attribution 4.0 License and code samples are licensed under the MIT License

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.