How does spring animation duration work?

I want to understand how spring animation works so I can replicate it on Android as a developer. I have already read documentations on Figma and this blog post How Figma put the bounce in spring animations

But the animation duration does not match my understanding of mass, spring, damper physics Mass-spring-damper model - Wikipedia. The expected animation duration should be much longer given the parameters stiffness, damping, mass.

For example, in the following screenshot, the duration is 400ms using stiffness = 80 damping = 30 and mass = 1.

But when the differential equations for mass, spring, damper is solved (see screenshot below using matlab and laplace transform method), it gives an approximate animation duration of 1.3615s, a much longer time than what is designed on figma.

So what is going on within figma? What mathematically model is used to calculate the animation duration? How can I implement the exact animation on android (or other platforms) provided by design?

Screen Shot 2022-08-31 at 11.50.19 AM

I don’t think they are using the classic MSD equations from that wiki page but this specific WebKit implementation:

https://svn.webkit.org/repository/webkit/trunk/Source/WebCore/platform/graphics/SpringSolver.h

And it sounds like (based on the blog post) they did additional tweaking so it may be tough to exactly map what you see in Figma to an implementation in Android.

But maybe you can ping Ricky or Willy on Twitter or Discord and they could give you a definite answer. I’m just guessing here.

@ntfromchicago Thank you for the information.

From what I can tell, the model in WebKit SpringSolver is a classic model of mass, spring, damper, with the same definition of w0 (w_n in wikipedia) and zeta

        m_w0 = std::sqrt(stiffness / mass);
        m_zeta = damping / (2 * std::sqrt(stiffness * mass));

How can I reach the devs on Discord? I think it’s also good to have a solution posted here for the benefit of the community.
Also it would be very important in general to be able to reliably translate design on figma to code, whether it’s android or other platforms, otherwise, what’s the point of designing on figma.

To join the Discord group: Friends of Figma

From there, you want to make sure to pick up the “Developers” interest role which will give you access to the dev-related channels. I’m sure one of the design/developer advocates could help point you in the right direction, especially since this relates to ensuring good handoff from design to development.

And thanks for looking into that WebKit code. It was hard for me to see how different/same it was to the wiki math page.

Thank you @ntfromchicago

I’m waiting for a reply from discord. In the meanwhile, I noticed that the WebKit code doesn’t implement the all the possible spring mass damper configurations. It’s specifically commented in that code

// Critically damped (ignoring over-damped case for now).

So, if figma is taking that code directly, it would also not implement some configurations correctly.

Hello! Just wanted to chime in.

The short answer:

Building spring animations from scratch can be pretty tricky. That’s why Android has a built-in spring library to help you with this!

You can configure it with two values:

Stiffness - This can be grabbed from Figma directly. In your case this is 80.
Damping Ratio - This can be calculated based on the values in Figma with the formula: damping / (2 * Math.sqrt(stiffness * mass)). We don’t show this to you currently, but maybe we should in the inspect panel. In your case, this comes out to 1.677, meaning it’s an overdamped spring. @Jiahao_Zhang is correct that Figma doesn’t handle overdamped springs, and instead limits the damping ratio to 1. To get the Android animation to look like Figma, you should use 1.

Note that it does NOT require duration! Why is this?

The long answer:

Duration is a tricky subject when it comes to springs. Unlike traditional bezier or keyframed animations, springs theoretically approach but never reach their final value.

The step time you see in matlab is based on an approach of 2% to the final value, in this case the time it takes to reach amplitude 0.98. If you were making an animation that travels 1000px and cut the animation off at 1.3615 seconds, it would stop 20px short of the final position, visibly snapping to the user. Not good.

Internally at Figma, we use a much tighter tolerance of 0.1%, meaning that an 1000px animation would stop within 1px of the final position. Most of this animation time isn’t actually “meaningful” animation as the object moves fairly little at the end of the long tail, but it’s necessary to ensure smoothness and avoid little janks and hiccups.

We don’t expose this duration directly because it’s an implementation detail. Other spring libraries such as React Spring and Framer Motion use a more dynamic system that looks at the object’s position and velocity to determine if the animation should end. In effect, objects that move shorter distances can stop animating earlier, saving CPU but breaking the notion of specifying a set duration. For this reason, most spring libraries don’t actually take in duration, but instead other, more fundamental constants such as Mass, Stiffness, Damping, and Damping Ratio.

But what is the duration you do see in Figma? In early prototypes, we surfaced the 0.1% duration to designers. However, it wasn’t very helpful because durations calculated this way tended to be seemingly random numbers (like 1361ms) and really really large, much longer than the animation “appeared” to take. This made it harder to share intuition between spring and bezier animations; for context, a 1000ms bezier animation would be considered really long. To address this, the duration you see today is carefully tuned constant to produce more “meaningful” and round numbers within the ballpark of bezier animations, even though the animation doesn’t technically end there.

2 Likes

Thanks @William_Wu for the info. I largely understand what you are trying to explain.

I see that the animations for underdamped and critically damped springs appear correct on figma. But as you confirmed Figma doesn’t handle overdamped springs, and instead limits the damping ratio to 1. Is that a design choice to not support it? Or is it a bug on figma that should be corrected in the future? I wonder if there should be a big red warning box when designers enter parameters that result in an overdamped configuration or something that prevents it from being configured.

Further into the overdamped situation, the duration shown is incorrect, no matter the definition. It is shorter than the critically damped situation. See this screenshot. How does spring animation duration work? - #2 by Jiahao_Zhang

Also see the recording, I think this shows that the duration is wrong, because it jumps when you drag the handle.

video

I have also done a bit of digging into figma_app.min.js and the duration is always calculated using the underdamped formula, which is invalid for overdamped situation. See this stackoverflow post. control - over and critically damped systems settling time - Electrical Engineering Stack Exchange

I would really hope figma would add support for overdamped springs, though I realise that might be a headache with existing designs on figma.

Best…

Other spring libraries such as React Spring and Framer Motion use a more dynamic system that looks at the object’s position and velocity to determine if the animation should end.

right, to add to that, android also uses distance relative to final position and velocity to determine the end of animation.

https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:dynamicanimation/dynamicanimation/src/main/java/androidx/dynamicanimation/animation/SpringForce.java;l=231

The duration is definitely a bit wonky in the overdamped situation. I think we mainly focused on underdamped and critically damped due to scoping - we thought that overdamped wouldn’t really get used that often, also because the Webkit library didn’t support it. It’d add more math and complexity to the product.

No promises on future support for overdamped springs, but fingers crossed that for the design you’re working on, you can get a similar looking animation with a critically damped spring with a longer duration.

2 Likes