The Spring Factory

Written by willsilversmith | Published 2015/09/02
Tech Story Tags: javascript | animation | web-development | stylus | web-design

TLDRvia the TL;DR App

Create spring animations using the physical spring equation.

Figure 1. A spring with damping 0.125, initial position 1, and 13 half cycles.

Spring effects, where an object’s property (most often position) oscillates in time, are very useful for adding a bouncy physical sensation to applications. Here we discuss how to model elasticity on physical springs and create a system that allows you to easily design your spring effect using time, the number of bounces, damping, and optionally initial position and initial velocity. Importantly, this spring library only handles the common oscillating case to make it easier to use.

Below are a video demonstrating the effect and a JSFiddle so you can play with the code. Read on to understand what makes the animation tick.

Indications for Use

This spring library specializes in designing bouncing animations of a known duration and number of bounces. For example, you might want elements on the screen to spring into place as they appear. With a fixed, precise duration, it can be easier to coordinate other animations along with it.

This spring implementation can be used, but is less effective, in modeling springs that react to unknown forces in real time as changes in force amplitude affect the settling time of the animation.

This spring implementation includes underdamped and undamped spring conditions, but does not model critically damped or overdamped conditions. While springs can be used effectively for these situations, in some cases it might be easier to reason about Ease In Out or Ease Out.

If you require these features, see the resources section below for other spring implementations.

Versus CSS Animations

Modern web browsers contain highly optimized graphics routines that you can access with CSS via the transition or animation directives. However, it’s hard to approximate a fully natural elastic effect using only bezier curves. Here’s an example. Look for the “bounce in right” animation.

Additionally, there are some places on a webpage where CSS cannot reach. In those situations, using the spring technique described here would be advantageous since you have to use Javascript anyway. Examples include animating scrollbars and canvases.

In the Implementation section below I provide a Stylus based CSS spring implementation.

How to Use It

// LISTING 1 (Javascript)// springFactory produces a function representing a spring.

var spring_easing = springFactory({halfcycles: 2, // accepts integers > 1damping: 0.15, // must be between 0 and 1 excluding 1,initial_position: 1, // typically in -1..1 inclusiveinitial_velocity: 0, // at start, how fast in either direction});

spring_easing(0.00); // 1.00000spring_easing(0.15); // 0.75796spring_easing(0.30); // 0.21033spring_easing(0.45); // -0.32669spring_easing(0.60); // -0.60467spring_easing(0.75); // -0.54533spring_easing(0.90); // -0.24334spring_easing(1.00); // nearly 0: 9.290299969698109e-17

Figure 2. A a graphic representation of the spring produced in Listing 1. For simplicity, only two half cycles were used.

The springFactory will produce a function that takes a parameter t that represents the percentage of the animation completed as a number between 0 and 1. It’s important to note that this derived function only produces a single number for each input t intended for use within an animation framework (that you might have to build). An example framework is included in the jsFiddle.

A unique aspect of this implementation is that the springs that springFactory produces are described mainly in terms of their outputs, duration, number of cycles, and damping, not the inputs that other springs commonly use like mass and stiffness. [1] This isn’t as good for physics simulations that have multiple interacting components, but its perfect for when you know exactly what you want. Furthermore, it always resolves to its final position, zero, in the specified time. This may look jerky if you don’t specify a sufficient number of cycles, but you can always add more.

You have the following controls on your spring:

  1. time (in milliseconds) — You can set the duration of the animation to be whatever you want. The longer the animation, the slower the spring moves. Note that this is something that you set in the animation framework, not the springFactory itself.
  2. halfcycles — an integer one or greater, it specifies how many times the spring will pass through the resting point before stopping. One halfcycle will come to rest the first time it snaps back. Two will continue on and rebound once, and so forth. In math terms, it’s the number of half periods of a sine wave.
  3. damping — a number between 0 (inclusive) and 1 (exclusive) [2] that primarily affects the decay of the motion. A damping of 0 will result in a perfectly elastic spring that seems like it will never stop (until it reaches the number of half cycles you specify, at which point it will abruptly stop). A damping close to 1 will create a nearly perfectly inelastic spring that doesn’t bounce at all. 0.15 is a pretty good value. Give it a shot.
  4. initial_position (optional, default 1) — it’s a number between -1 and 1. A spring at rest is located at 0, and at 1 is considered fully extended. Imagine taking a spring, pulling it, then letting it go. The height at which you do so is represented by this number.
  5. initial_velocity (optional, default 0) — This one is a little trickier to understand. If you put a ball on the top of the spring, and threw it, this would be how fast it was moving. It’s common for the spring to simple be released from rest, in which case this parameter is 0. When you do want it to move, you’ll have to feel this one out for yourself, but common values might be between -2 to 2. You can think of it as being roughly on the same scale as initial_position, but with opposing direction.

Here is an example describing a spring that begins fully extended at rest and is released. It then performs two half cycles with a damping constant of 0.15.

The Spring Designer

While using this spring framework for a project, I found it helpful to use a Grapher file:

Download Spring Designer Grapher File (for OS X)

The grapher file allows you to plot your springs and tweak them very easily before committing to the process of feeling them out more laboriously. For reasons involving numerical solutions that will become clear later, this file does not include a parameter for initial velocity. [3] In the file, k is the number of half cycles, ζ (zeta) is the damping coefficient, and y0 is the initial position.

The graph in Fig. 2 was produced using the above file.

The Theory

Based on a short survey of Google, the main technique to achieve a springy effect is to use a decaying sine or cosine wave. “Realistic Bounce and Overshoot” goes into good detail about this, but let me recapitulate the main point here before we go further.

Equation 1. A decaying cosine wave.

Here, x is the distance traveled, t is time, k is a decay constant, and ω (omega) is the frequency of the sinusoid. It’s multiplied by 2π to make ω = 1 a full cycle rather than having to remember to account for it in ω. Here’s what it looks like in a graph (holding k and ω to be 1 for simplicity).

Figure 3. A damped sinusoid approximates a spring.

Note that the graph begins at one, falls, overshoots 0, but doesn’t go all the way to -1. It slowly decays to its steady state value of 0. We can use sine or cosine depending on whether we want to start at 1 or 0. [4]

You can tweak k and ω to achieve a fairly lifelike bounce. However, if you want to accurately simulate a physical spring, things get a bit more complex. The major advantages of doing so are adding a subtle modification to the waveform, and the ability to kick off the spring with an initial velocity.

Introducing ζ (zeta), the damping coefficient. This variable represents how much elasticity is in our spring. When ζ is greater than one, the spring is overdamped and does not oscillate. When it equals one, it’s called critically damped and does not oscillate. We are concerned with the case of 0 < ζ < 1, underdamped, where it will oscillate but decay towards zero. Consulting wikipedia, here are the equations for the underdamped case:

Equation 2. The underdamped spring equation.

Equation 3. Coefficient A in Eqn. 2

Equation 4. Coefficient B in Eqn. 2

In Eqn. 2, you can see that the real underdamped spring equation is a linear combination of sine and cosine, damped exponentially. k from Eqn. 1 has become 2πωζ and the argument to cosine has acquired a prefix of sqrt(1–ζ^2). Let’s dissect what’s going on. 2π allows ω to become a easily controllable knob as 1 would be a complete cycle. ζ does double duty, affecting both the damping and tweaking the frequency of the oscillation. t, the independent variable, varies between 0 to 1 — a complete animation.

An important consideration is that we’d like the curve to pass through 0 when t = 1, otherwise the animation will, depending on the framework, either come to a halt off target or jump very suddenly to the final position. This means that only some values of ω are allowed. We can set the “no slip” boundary condition y(1) = 0, and solve for ω: [5]

Equation 5. Solution for ω such that y(1) = 0, that is to say, there is no slip at the end of the animation. A and B are defined in Eqn. 3 and 4 respectively.

This is great, but there’s one nettlesome detail. ω is defined in terms of B and B is defined in terms of ω. There are a lot of details here, but simplifying the situation to make it more clear, it boils down to solving this equation: w = tan(w), which has no analytic solution.

However, all is not lost. For one, if the initial velocity is 0, B simplifies to an equation that does not depend on ω:

Equation 6. Eqn. 4 with 0 initial velocity.

In this special case, luckily a common use case, we have a fully analytic spring solution.

However, all is not lost in the general case. Luckily, B and ω can be solved for using numerical methods. I used the simple bisection method, which is known to be somewhat slow, but in this case, rapidly converges in a relatively few steps. Importantly, this numerical solution happens once at spring creation, not while the spring is actually being used, so the performance impact is minimal.

Here’s an example of what this looks like when you impart the spring with an initial velocity. Fig. 4 is the same as Fig. 2, which had initial velocity 0, but with an initial velocity of 2. Note that the start of the animation is rising instead of falling, and the range is not bounded between -1 to 1.

Figure 4. A spring with initial velocity 2, damping 0.15, halfcycles 2, and initial position 1.

We now have five easily controllable knobs to tune our real spring:

  1. ζ — The spring quality, 0 to 1. (0 being maximum elasticity) [6]
  2. k — The number of half cycles to perform before coming torest at t = 1. You can also think of this as number of zero crossings.

  3. t — t runs from 0 to 1, but this is only a percentage. You can adjust the length of the animation to be anything you want.
  4. initial position, y(0) — You can start your spring at any height, but generally you’ll want to keep it at 1 because you can scale the output after use.
  5. initial velocity, dy/dt (0) — This allows you to distort the natural motion of the spring by driving it by some amount of force (e.g. motion from user input).

Implementation

Below is the implementation of the springFactory. It’s notable that if we only support the common special case that there is zero initial velocity, the size of the implementation could shrink by over 50% as the numerical solution for B and ω takes up about 100 lines.

You can find a Stylus CSS Preprocessor implementation in terms of CSS Animations here:

Animation

Before we go on, a little about how to turn this equation into an animation. While the math applies to any language, let’s use javascript because it’s popular. See the aside, animation.js, for a basic framework for animating an object. [7]

In this article, we’re focusing on the easing function. An easing function provides the proportion of the distance between start and end to cross at a given moment. Sometimes it can be positive and the animation moves forward, sometimes it can be negative and the animation will appear to run backwards. The framework shown is simply to put the easing function in context.

On line 25, you can see that t is the proportion of the animation completed, a value in [0, 1], and is provided to the easing function.

On line 14, you can see the default value for the easing function is a linear ease. That is to say the animation will progress in direct proportion to the progression of time.

Novelty Statement

I hesitate to state that these aspect of this implementation are truly novel, but after evaluating a number of spring libraries, I haven’t yet seen them in this form yet. There’s a chance that in the domain of Javascript underdamped spring formulations built for the web, the combination of the features below may be unique.

Analytic Solution

While specifying an initial velocity requires a numerical solution for the coefficient of the sine, on the whole, the equation can be easily analyzed using standard algebraic tools. This enables the animation to be trivially jumped to any point, run forward or backward, started, stopped, or resumed at any time.

Most, but not all, other implementations appear to rely on performing a physical simulation of the forces in the viscous mass-spring-damper model, or by solving the ordinary differential equation using the RK4 method.

The physics simulation based method can run forwards, and possibly backwards, but skipping requires running the simulation very quickly — a low performance condition.

On Time Settling

The algebraic and numerical solutions for omega and B, as described in this article, guarantee that the spring will be located at 0 when t = 1. Other solutions either do look for convergence in their algorithm, but this sometimes occurs at the expense of losing control of the duration or stopping point of the animation, requiring fiddling with the numbers until it looks right.

Simpler Parameters

While many frameworks provide the ability to call a function with no arguments and get some kind of spring out of it, it’s still fair to compare them on how much work it takes to fully specify a particular one. This framework requires three parameters: duration, halfcycles, and damping, though you can go up to five with initial position and velocity.

The most common complete alternative set is duration, mass, tension (stiffness), friction (viscosity), initial position, and initial velocity.

Versus the complete set, this formulation has one fewer parameter, though it would be fair to allow mass be 1 and only vary tension and friction. Nonetheless, the relative complexity of reasoning about these parameters is different.

In this formulation, duration, half cycles, and initial position are easy to reason about. You can predict the effects of changing them without the aid of tools. The effects of adjusting damping and velocity requires more guess and check unless you’re prepared to start calculating.

In the most common alternative, duration and initial position are still easy to reason about, but the effects of adjusting mass, tension, friction, and initial velocity require guess and check.

Performant Math

On a Macbook Pro 13", Late 2013 with 2x 2.8 GHz Intel i7 cores, the following code was tested:

var easing = springFactory({damping: 0.2,halfcycles: 15,initial_position: 0.5, // +50% screen, from centerinitial_velocity: 0,});

for (var t = 0; t < 1; t += 1 / 1e8) {easing(t);}

With the following results on node version 5.1.1:

$ time node springFactory.js

real 0m6.136s

user 0m6.104s

sys 0m0.027s

I also ran an unladen for loop control group test, which took 0.220s. This confirms that the JIT optimizer was not removing the easing function call due to lack of side effects.

That equates to about 16.3 million updates a second. If you run at 60 fps, the spring math itself would support up to about 271,000 springs on this laptop if rendering them and calling lots of different functions took no overhead; an unrealistic situation, but useful for comparisons.

Critically Damped and Overdamped Springs Discarded

I have no empirical proof, but my gut tells me most designers are looking for a bounce when they reach for springs. It is possible that eliminating non-oscillating spring conditions may improve ease of use by preventing confusion and reducing the required learning time until mastery.

One could reasonably criticize this point as mistaking a defect for a feature, but I’m interested in seeing how it fares in the real world.

Grapher File

The analytic solution allows for Grapher, a plotting program installed by default on OS X, to serve as a visual aid in designing your spring.

If this piece interested you, you might like Ease-In-Out: The Sigmoid Factory or The Bounce Factory.

Resources

  1. See this framework play in a JS Fiddle.
  2. Spring Designer Grapher File (for OS X)
  3. Robert Penner’s, the man who first named easing equations like “ease-in-out” freely shares the dynamic motion section on his book. It’s a little dated (it was originally written for ActionScript, a JS like language designed for Flash), but the non-Flash logic is still sound (particularly the first half of the chapter). His code is a little over-optimized, making it hard to read, but he was writing for much slower machines back in the day.
  4. http://easings.net/ — A set of timing functions you can play with and use in CSS animations. They’re described by bezier curves, so they’re a bit harder to plug and play with Javascript animations unless you use a bezier library. This is an exercise left to the reader.
  5. Runge-Kutta 4th Order (RK4) Solver — Javascript implementation, in case you’re interested in making your own spring library.

Endnotes

[1] Some example frameworks:

RBBAnimation — Requires damping, mass, and stiffness. Uses standard differential equation solution, but doesn’t force spring to resolve at a particular time of your choosing.

Intuit Animation Engine — Requires damping, mass, and stiffness. Uses RK4 to automatically get solutions for overdamped, and critically damped as well as underdamped.

Facebook POP — Seems to require only springBounciness, animation start and ends. I don’t understand iOS core animation well enough to interpret its API fully, but internally it seems to plug and chug on differential equations until convergence.

Velocity.js — Requires duration, tension, friction. Uses RK4. Runs two passes to figure out how to make the spring converge within a set duration.

[2] Damping allows you to describe how “bouncy” or “elastic” the spring is and is represented by the variable ζ (zeta). If ζ > 1, the spring is “overdamped” and comes to a rest without bouncing. If ζ = 1, it’s called “critically damped” and it comes to a rest in the minimum time with a slight overshoot. If 0 < ζ < 1, it’s called “underdamped” and will oscillate while decaying over time towards zero. If ζ = 0, the oscillation will occur without decay and is called “undamped”. In this article, we are principally concerned with the underdamped case.

[3] Okay, you got me. I do have a grapher file that allows you to figure out how initial velocity will look, but it’s hard to use. Essentially, you pick an initial velocity v0, then you see if you can get a line representing error to hit zero by guessing values for the variable B.

Download Spring Designer with Initial Velocity

[4] Recall that sin(x) = cos(x-π/2). You can adjust the sinusoid’s starting position by adding a phase shift φ (phi) like so: sin(x + φ)

In a slight elaboration, sin(_f_x), where f is the frequency multiplier, can be phase shifted as sin(_f_x + φ).

[5] Here is the algebra.

[6] This might seem a little awkward. Shouldn’t 1 be maximum elasticity?

ζ comes from the physics community who were interested in how quickly things decay. You can make a new variable, ɛ (epsilon), to use in place of ζ as ɛ = 1-ζ that will be least elastic around 0 and most elastic at 1. As an aside to this aside, note that at ɛ = 0 (or ζ = 1), Eqn. 2 breaks down because the cosine argument zeros out. you can get as close as you want to zero but you can’t go there.

[7] Where available, you’ll want to use window.requestAnimationFrame. This is a modern method for timing renders as compared to setInterval. It provides a consistent refresh rate synced to the browser’s refresh rate and respects battery life. I’ve tried both methods and setInterval is invariably jerky. Note that cancelAnimationFrame exists just as clearInterval does.


Published by HackerNoon on 2015/09/02