Animation Timing-Functions Get Applied Per-Keyframe In CSS
A couple of weeks ago, I looked at creating a simple slide-show using dynamic keyframe animations in Angular 10.0.9. In the video for that demo, I mentioned that my approach was somewhat limited because the timing-function wasn't granular enough. As it turns out, my assumption of granularity was completely wrong. In fact, as I just learned from Una Kravets on the Animations episode of the CSS Podcast, the animation-timing-function
property is applied on a per-keyframe basis. To help correct my poor mental model, I put together a small demo showcasing this per-keyframe granularity.
Run this demo in my JavaScript Demos project on GitHub.
View this code in my JavaScript Demos project on GitHub.
To demonstrate that the animation-timing-function
within a CSS animation is applied on a per-keyframe basis, I am going to use cubic-bezier()
function that moves a dot across the screen with a sort-of "bouncing" effect. This way, we can see that the dot "bounces" beyond the bounds of each keyframe configuration - not just beyonds the bounds the overall animation.
In the following CSS animation, the dot will rest at about 1/3 the horizontal distance; then again at about 2/3 the horizontal distance. At each resting point, we should see the cubic-bezier()
animation-timing-function
"bounce" the dot beyond the target left
setting.
<!doctype html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8" /> | |
<title> | |
Animation Timing-Functions Get Applied Per-Keyframe In CSS | |
</title> | |
</head> | |
<body> | |
<h1> | |
Animation Timing-Functions Get Applied Per-Keyframe In CSS | |
</h1> | |
<style type="text/css"> | |
.track { | |
border: 1px solid red ; | |
border-radius: 100px ; | |
height: 100px ; | |
position: relative ; | |
} | |
.marker { | |
background-color: red ; | |
bottom: 0px ; | |
position: absolute ; | |
top: 0px ; | |
width: 1px ; | |
} | |
.dot { | |
background-color: #212121 ; | |
border-radius: 80px ; | |
height: 80px ; | |
left: 10px ; | |
position: absolute ; | |
top: 10px ; | |
width: 80px ; | |
z-index: 2 ; | |
/* | |
In order to demonstrate that the timing-function executes per-keyframe, | |
I'm going to use an exaggerated cubic-bezier configuration that "bounces" | |
the dot past the final "left" value of each keyframe. | |
*/ | |
animation-direction: alternate ; | |
animation-duration: 10s ; | |
animation-iteration-count: infinite ; | |
animation-name: box-animation ; | |
animation-timing-function: cubic-bezier( .55, -0.64, .42, 1.63 ) ; | |
} | |
@keyframes box-animation { | |
from, 5% { | |
left: 10px ; | |
} | |
25%, 40% { | |
left: 30% ; | |
} | |
60%, 75% { | |
left: 70% ; | |
} | |
95%, to { | |
left: calc( 100% - 90px ) ; | |
} | |
} | |
</style> | |
<div class="track"> | |
<div class="dot"> | |
<br /> | |
</div> | |
<!-- These markers help show the movement of the dot. --> | |
<div class="marker" style="left: calc( 30% + 40px ) ;"></div> | |
<div class="marker" style="left: calc( 70% + 40px ) ;"></div> | |
</div> | |
</body> | |
</html> |
And, when we run this CSS animation in the browser, we get the following output:

As you can see, the dot "bounces" beyond the target left
alignment of each CSS keyframe. That's because the animation-timing-function
is being applied on a per-keyframe basis - not to the entire animation life-span. This is a small but critical difference in how keyframe animations work. And now, hopefully, I'll remember this going forward.
Want to use code from this post? Check out the license.
Reader Comments
@All,
As a fast-follow to this post, I wanted to demonstrate that CSS Animation timing-functions can actually be changed per-keyframe:
www.bennadel.com/blog/3886-animation-timing-functions-can-be-changed-per-keyframe-in-css.htm
That's actually the piece of information that Una mentioned in the CSS Podcast - that the
animation-timing-function
can be overridden within a key-frame; which, in turn, led me to understand that the timing-function is also applied per-keyframe as well. Overall, it seems that CSS Animations are just a lot more powerful and dynamic than I had given them credit for.