Over the previous few years, thereβs been lots of discuss and experimentation with scroll-driven animations. Itβs a really shiny function for certain, and as quickly because itβs supported in Firefox (with no flag), itβll be baseline. Itβs a part of Interop 2026, in order that ought to be comparatively quickly. Basically, scroll-driven animations tie an animation timelineβs place to a scroll place, so if you happen to had been 50% scrolled you thenβd even be 50% into the animation, they usuallyβre surprisingly straightforward to arrange too.
Iβve been seeing important curiosity within the new CSS corner-shape property as properly, though it solely works in Chrome for now. This permits us to create corners that arenβt as rounded, or arenβt even rounded in any respect, permitting for some intriguing shapes that take little-to-no effort to create. Whatβs much more intriguing although is that corner-shape is mathematical, so itβs simply animated.
Therefore, say good day to scroll-driven corner-shape animations (requires Chrome 139+ to work absolutely):
corner-shape in a nutshell
Actual fast β the totally different values for corner-shape:
corner-shape key phrase |
superellipse() equal |
|---|---|
sq. |
superellipse(infinity) |
squircle |
superellipse(2) |
spherical |
superellipse(1) |
bevel |
superellipse(0) |
scoop |
superellipse(-1) |
notch |
superellipse(-infinity) |

However whatβs this superellipse() perform all about? Nicely, principally, these key phrase values are the results of this perform. For instance, superellipse(2) creates corners that arenβt fairly squared however arenβt fairly rounded both (the βsquircleβ). Whether or not you employ a key phrase or the superellipse() perform straight, a mathematical equation is used both means, which is what makes it animatable. With that in thoughts, letβs dive into that demo above.
Animating corner-shape
The demo isnβt too sophisticated, so Iβll begin off by dropping the CSS right here, after which Iβll clarify the way it works line-by-line:
@keyframes bend-it-like-beckham {
from {
corner-shape: superellipse(notch);
/* or */
corner-shape: superellipse(-infinity);
}
to {
corner-shape: superellipse(sq.);
/* or */
corner-shape: superellipse(infinity);
}
}
physique::earlier than {
/* Fill viewport */
content material: "";
place: fastened;
inset: 0;
/* Allow click-through */
pointer-events: none;
/* Invert underlying layer */
mix-blend-mode: distinction;
background: white;
/* Donβt overlook this! */
border-bottom-left-radius: 100%;
/* Animation settings */
animation: bend-it-like-beckham;
animation-timeline: scroll();
}
/* Added to playing cards */
.no-filter {
isolation: isolate;
}
Within the code snippet above, physique::earlier than mixed with content material: "" creates a pseudo-element of the with no content material that’s then fastened to each fringe of the viewport. Additionally, since this animating form shall be on prime of the content material, pointer-events: none ensures that we will nonetheless work together with stated content material.
For the formβs shade Iβm utilizing mix-blend-mode: distinction with background: white, which inverts the underlying layer, a classy impact that to a point solely maintains the identical degree of shade distinction. You gainedβt need to apply this impact to every part, so right hereβs a utility class to exclude the impact as wanted:
/* Added to playing cards */
.no-filter {
isolation: isolate;
}
A comparability:

Youβll want to mix corner-shape with border-radius, which makes use of corner-shape: spherical below the hood by default. Sure, thatβs proper, border-radius doesnβt really spherical corners β corner-shape: spherical does that below the hood. Reasonably, border-radius handles the x-axis and y-axis coordinates to attract from:
/* Syntax */
border-bottom-left-radius: ;
/* Utilization */
border-bottom-left-radius: 50% 50%;
/* Or */
border-bottom-left-radius: 50%;

In our case, weβre utilizing border-bottom-left-radius: 100% to slip these coordinates to the other finish of their respective axes. Nonetheless, weβll be overwriting the implied corner-shape: spherical in our @keyframe animation, so we seek advice from that with animation: bend-it-like-beckham. Thereβs no must specify a length as a result of itβs a scroll-driven animation, as outlined by animation-timeline: scroll().
Within the @keyframe animation, weβre animating from corner-shape: superellipse(notch), which is like an inset sq.. That is equal to corner-shape: superellipse(-infinity), so itβs not really squared nevertheless itβs so aggressively sharp that it seems squared. This animates to corner-shape: superellipse(sq.) (an outset sq.), or corner-shape: superellipse(infinity).
Animating corner-shape⦠revisited
The demo above is definitely a bit totally different to the one which I initially shared within the intro. It has one minor flaw, and Iβll present you the right way to repair it, however extra importantly, youβll be taught extra about an intricate element of corner-shape.
The flaw: at the start and finish of the animation, the curvature seems fairly harsh as a result of weβre animating from notch and sq., proper? It additionally seems like the form is being sucked into the corners. Lastly, the form being caught to the edges of the viewport makes the entire thing really feel too contained.
The answer is straightforward:
/* Change this... */
inset: 0;
/* ...to this */
inset: -1rem;
This stretches the form past the viewport, and though this makes the animation seem to begin late and end early, we will repair that by not animating from/to -infinity/infinity:
@keyframes bend-it-like-beckham {
from {
corner-shape: superellipse(-6);
}
to {
corner-shape: superellipse(6);
}
}
Certain, which means that a part of the form is at all times seen, however we will fiddle with the superellipse() worth to make sure that it stays exterior of the viewport. Right hereβs a side-by-side comparability:

And the unique demo (which is the place weβre at now):
Including extra scroll options
Scroll-driven animations work very properly with different scroll options, together with scroll snapping, scroll buttons, scroll markers, easy textual content fragments, and easy JavaScript strategies corresponding to scrollTo()/scroll(), scrollBy(), and scrollIntoView().
For instance, we solely have so as to add the next CSS snippet to introduce scroll snapping that works proper alongside the scroll-driven corner-shape animation that weβve already arrange:
:root {
/* Snap vertically */
scroll-snap-type: y;
part {
/* Snap to part begin */
scroll-snap-align: begin;
}
}
βMaskingβ with corner-shape
Within the instance under, Iβve basically created a border across the viewport after which a notched form (corner-shape: notch) on prime of it thatβs the identical shade because the background (background: inherit). This form fully covers the border at first, however then animates to disclose it (or on this case, the 4 corners of it):
If I make the form a bit extra seen, itβs simpler to see whatβs taking place right here, which is that Iβm rotating this form as properly (rotate: 5deg), making the form much more fascinating.

This time round weβre animating border-radius, not corner-shape. Once we animate to border-radius: 20vw / 20vh, 20vw and 20vh refers back to the x-axis and y-axis of every nook, respectively, that means that 20% of the border is revealed as we scroll.
The one different factor price mentioning right here is that we have to fiddle with z-index to make sure that the content material is greater up within the stacking context than the border and form. Apart from that, this instance merely demonstrates one other enjoyable means to make use of corner-shape:
@keyframes tech-corners {
from {
border-radius: 0;
}
to {
border-radius: 20vw / 20vh;
}
}
/* Border */
physique::earlier than {
/* Fill (- 1rem) */
content material: "";
place: fastened;
inset: 1rem;
border: 1rem strong black;
}
/* Notch */
physique::after {
/* Fill (+ 3rem) */
content material: "";
place: fastened;
inset: -3rem;
/* Rotated form */
background: inherit;
rotate: 5deg;
corner-shape: notch;
/* Animation settings */
animation: tech-corners;
animation-timeline: scroll();
}
primary {
/* Stacking repair */
place: relative;
z-index: 1;
}
Animating a number of corner-shape parts
On this instance, we now have a number of nested diamond shapes because of corner-shape: bevel, all leveraging the identical scroll-driven animation the place the diamonds enhance in measurement, utilizing padding:
@keyframes diamonds-are-forever {
from {
padding: 7rem;
}
to {
padding: 14rem;
}
}
#diamonds {
/* Middle them */
place: fastened;
inset: 50% auto auto 50%;
translate: -50% -50%;
/* #diamonds, the s inside */
&, div {
corner-shape: bevel;
border-radius: 100%;
animation: diamonds-are-forever;
animation-timeline: scroll();
border: 0.0625rem strong #00000030;
}
}
primary {
/* Stacking repair */
place: relative;
z-index: 1;
}
Thatβs a wrap
We simply explored animating from one customized superellipse() worth to a different, utilizing corner-shape as a masks to create new shapes (once more, whereas animating it), and animating a number of corner-shape parts directly. There are such a lot of methods to animate corner-shape apart from from one key phrase to a different, and if we make them scroll-driven animations, we will create some actually fascinating results (though, theyβd additionally look superior in the event that they had been static).









