Realistic animations with CSS easing functions: Bouncing balls, pendulums and tossing books

Realistic animations with CSS easing functions: Bouncing balls, pendulums and tossing books


12 min read

CSS gives us a list of built-in easing functions for animations, but did you know that they mimic the movement of real world objects - like a bouncing ball or a pendulum?

Under the hood, the timing-functions like ease-in or ease-in-out are just cubic-bezier curves with predefined parameters, but those parameters aren't random at all.

Follow me on a journey through the CSS timing functions and discover how they relate to objects moving in gravity - the good news is, they do all the physics stuff for you, so you won't have to!

Note: This article assumes that you're already familiar with cubic-bezier timing-functions in CSS animations. If you'd like to learn the fundamentals, or need a little refresher, I've written a detailed introduction to cubic-bezier timing-functions in a previous article -> Mastering cubic-bezier timing-functions: An easy guide

Bouncing ball

Bouncing ball with keyframes

Let's jump right in with a full dose of physics 🤟

I know I just said we won't need physics, but it's a simple calculation, so please bear with me - going through this process once will help us understand later where the parameters of the built-in CSS timing-functions are coming from.

If an object falls in a gravitational field, the distance that it has fallen is proportional to the time squared:

distance ~ t²

Using that, we can write a bouncing-ball keyframe-animation like this:

@keyframes bounce-keyframes {
  0%:   {top: 0%}
  10%:  {top: 1%}
  20%:  {top: 4%}
  30%:  {top: 9%}
  40%:  {top: 16%}
  50%:  {top: 25%}
  60%:  {top: 36%}
  70%:  {top: 49%}
  80%:  {top: 64%}
  90%:  {top: 81%}
  100%: {top: 100%}

.ball {
  animation: bounce-keyframes 0.6s linear infinite alternate;

Result - a perfect bouncing ball:

But do we really need to write out that whole set of keyframes for a simple bouncing ball? Of course not 😏

Discover the power of timing-functions

The above approach asks "How far has an object fallen after x% of the total time?". Then we calculate those distances for 10 time values, so we get 10 pairs of time/distance values. With those, the animation already runs fairly smooth, and it would be even smoother if we added more value pairs.

However, one could also ask "How much time has passed after the object has fallen y% of the total height?". Instead of manually calculating a number of time/distance value pairs, we only need one timing-function, and get a practically infinite number of keyframes. Animation smoothness:👉 💯

Bouncing ball with ease-in and ease-out

Most of you probably know already that we could've achieved the same movement using the built-in CSS cubic-bezier functions ease-in or ease-out.

One keyframe, one timing function, no math:

@keyframes bounce-fall {
  0%:   {top: 0%}
  100%: {top: 100%}
@keyframes bounce-rise {
  0%:   {top: 100%}
  100%: {top: 0%}

.ball-left {
  animation: bounce-fall 0.6s ease-in infinite alternate;
.ball-right {
  animation: bounce-rise 0.6s ease-out infinite alternate;

These two timing-functions are symmetrical. We can either animate the falling part with ease-in, or the rising part with ease-out. Setting the animation-direction property to alternate provides the second half of the movement.

The point to take away from this is that the CSS timing functions help us create animations that look real and natural. ease-in and ease-out describe a quadratic relation of time and property. If that property is something like a vertical distance/height, the timing-function describes how an object falls in a gravitational field.

Combining ease-in and linear

Making the ball bounce over to the right can be done by superposition of the falling animation with a linear movement:

@keyframes bounce-ease-in {
  0%:   {top: 0%}
  100%: {top: 100%}
@keyframes move-right {
  0%:   {left: 0%}
  100%: {left: 100%}

.ball {
    bounce-ease-in 0.6s ease-in infinite alternate,
    move-right 3s linear infinite;

Another use case - ball rolling down a ramp:


So far, we've covered linear, ease-in and ease-out. What about ease-in-out?

The image below shows all three curves in one plot:

  • start point handle of ease-in-out = start point handle of ease-in
  • end point handle of ease-in-out = end point handle of ease-out


ease-in-out is a combination of both - and results in a symmetrical (sine) curve (see a demo here on codepen).

So this timing-function could for example describe the movement of a pendulum, when the animated property is the angle of rotation:

The mysterious ease function

This timing-function puzzles me a little. As seen above, the other easing functions have simple real-world equivalents:

  • the quadradic-bezier-curves for dropping an object or throwing it upwards (ease-in and ease-out)
  • the cubic-bezier-curve for a periodic movement governed by a sine-function (ease-in-out)

I couldn't find any hints where the parameters of the ease function are coming from, though. Looking at the curve and the resulting animation:

  • something starts with an initial speed > 0
  • it accelerates for a short while (about 25% of the total animation time)
  • for the remaining 75% of time, it gradually slows down until its speed is 0

Trying to come up with a real-world example for this function: Tossing a book on a table. While my hand still holds the book, it accelerates. After throwing it, it slides over the table and quickly slows down due to friction.

However, this seems a little random, doesn't it? If you have a better explanation concerning the parameters of the ease timing-function, please leave a comment below! 👇

Realism in animations

I'd like to go back to the example above, where we calculated the keyframes for a bouncing ball. The formula we applied was:

distance ~ time²

This formula is universally true for every bouncing ball on every planet. Can we modify it so it describes a bouncing ball on earth?

Prepare for a weird exercise 🙃

Dropping a ball on earth

To make the above calculation specifically with earth's gravity, we'll have to add a factor to account for the gravitational acceleration on earth (g = 9.81 m/s²):

distance = 0.5 · g · time²

Let's say I'm animating an element that falls a distance of 300px. Assuming a monitor resolution of 100px/inch, my 300px would equal 3 inches (7,62 cm). Calculating the time (= animation-duration):

t = √(2 · distance / g) = 0.125 s

Alright, here's the resulting animation. Does that look realistic? 😳

Yes, stuff really falls that fast! If you don't believe me, grab a rubber or pen and let it fall in front of your screen.

Importance of scale/dimension

The reason why it looks unrealistic while it is realistic is that we're used to seeing things on a screen that are actually much bigger. If we give a scene some context by adding visual elements that define the dimensions to apply, they make sense again.


See the below animation of a basketball. The hoop is approximately 3 meters high, so the ball falls for 0.78s - and the animation looks perfectly realistic to our eyes:


We can do the same "realistic" calculations with our pendulum to find out the exact time period to apply. We'll need a little bit of theory first, though:

It's a bit counter-intuitive, but for the time period of a pendulum, it doesn't matter how heavy the ball is. It's also irrelevant if we let it start swinging at a small or a large angle.

The only things that define the time period of a pendulum are the length of the string and the gravitational acceleration g:

T = 2 · π · √ (length/g)

Assuming that we're staying on earth with our animations, the only question is - how long is that string?

Again, adding some visuals to indicate the dimensions to apply:


  • the CSS timing-functions ease-in and ease-out describe how an object falls under the influence of gravity
  • the ease-in-out timing-function describes a periodical movement according to a sine-function (like a pendulum under gravity)
  • given fixed dimensions, the strength of the gravitational force can be modified by changing the animation-duration
  • animations look realistic if the dimensions and time values are chosen such that the resulting movement meets the (implicit) expectations of the viewer
  • for completeness: a full list of all timing functions in MDN docs

Thank you for reading!

I hope you enjoyed this little trip through the CSS timing-functions. Please leave any comments or feedback below, or @ me on Twitter - especially if you can shed some light on the mystery of the ease function parameters! If you liked this post, I invite you to subsribe to my newsletter. Until next time 👋