We want to design motion that feels natural and familiar. While using the right type of easing already helps, these easings are made based on a curve and a duration. That means that we can’t create a perfectly natural motion, because the movement in the world around us doesn’t have a fixed duration.
Notice how with CSS animations and transitions we always have to provide a duration.
.element {
animation: scaleUp 0.3s ease-out;
}
@keyframes scaleUp {
0% {
transform: scale(0);
}
100% {
transform: scale(1);
}
}
Spring animations can help here as they are based on the behavior of an object attached to a spring in a physical world, so it feels more natural by definition. They also don’t have a duration, making them more fluid.
The example below (that I borrowed from Josh Comeau, because it’s amazing), shows the difference between a spring and an ease
animation.
Notice how the bottom ball slows down more naturally.
Spring animations are heavily used in iOS; they’re also the default animation in SwiftUI. When I tried recreating the Dynamic Island for the web, I said that it feels like a living organism. It’s mainly because of the spring animation being applied there.
Like we said before, spring animations are created by describing the behavior of an object attached to a spring. This is done by providing values for mass, tension, and velocity.
These properties can be quite unintuitive, as there is no real object with mass or a spring with stiffness here, so I created a visualizer to help you understand how they influence the animation. This visualizer is heavily inspired by morse’s work.
I use this visualizer every time I need a custom spring animation.
Apple has come up with an alternative method for configuring springs, based solely on duration and bounce. Framer Motion allows you to define a spring animation with the same approach.
This means that we start using the duration parameter again, but here it refers to the perceptual duration, which is the time it takes for an animation to feel like it’s finished, even though there’s still some very subtle movement happening.
This allows us to create more natural movement, without sacrificing the duration parameter. It’s also easier to understand and use, as we don’t have to think about mass, tension, and velocity.
Sometimes, when an animation hasn’t yet finished, we need to redirect it. When that happens, a spring animation uses the velocity it had when it was re-targeted making the movement feel smooth and natural.
Click around in the demo below, notice how the ball changes its movement without losing the momentum. This wouldn’t be possible with a CSS animation.
When I was making Sonner, I made the mistake of using a CSS animation for the enter transition. What happened then was, if I quickly added two toasts, the first toast would jump to its new position because CSS animations are not interruptible.
Try adding toasts quickly and notice how they jump to their new positions.
While spring animations can have a bouncy effect, there are only a few instances in product UI where a bounce is appropriate. For a more physical feel, a slight bounce at the end of a drag gesture might make sense. Notice how when I drag it to dismiss we have a slight bounce at the end. However, if I simply press to close, there’s no bounce at all.
The above works, because while you can’t see it, the user has to drag to dismiss. A drag requires some force, you need to drag your finger over the screen, so adding a bounce makes this transition feel more natural. Just like throwing a ball against the wall.
I tend to avoid bounce in most cases, and if I do decide to use it, it’s a very small value. Generally, having no bounce at all should be your default to ensure that your transitions feel natural and elegant.
You might be thinking, why use easing if spring animations feel more natural? Unfortunately, creating actual spring animations in CSS is currently impossible. You can only mimic spring animations using the linear()
function, but this remains just an approximation.
Bounce made with linear easing by jhey.
Libraries such as Framer Motion or React Spring can help, though their file sizes are quite large. You don’t necessarily need spring animations for simple transitions like color or opacity changes, but they can improve animations that involve more motion like the trash interaction below. It’s a trade-off that requires consideration.
Click on the pictures and then on the trash icon to see it animate.
I wanted it to feel like the iOS Sheet component, which utilizes spring animations. This would mean that I’d need to use a library like Framer Motion. However, I wanted to keep Vaul’s package size small, so using a large library wasn’t really an option. I decided to prioritize a smaller package size over a more native feel in this case.
Notice the small bounce that gets applied on iOS when you interact with snap points above? That’s not really possible with Vaul, because we don’t use spring animations.
You can also use spring animations in Figma. They wrote a great article about the implementation of spring animations in their design tool. How Figma put the bounce in spring animations
We’ve now covered the theory of spring animations, and even though we have also seen some examples, you might still have some questions. That’s why we will build a few components that rely heavily on spring animations soon, the Dynamic Island is one of them.
For now, I highly recommend visiting the links in the resources section as they contain a lot of valuable information about spring animations.
I’d love to hear your feedback about the course. It’s not required, but highly appreciated.