The Calculus

If you’ve learned some elementary calculus, you’d probably know that we can represent the position of something in motion with a function of time.

Take the derivative of the position, and you get the velocity.

Take the derivative of the velocity, and you get the acceleration.

Recall this motion equation from the previous section:

Substitute x, v, and a. Now you have this:

Now, this is a differential equation. The solutions to this equation would be the function f(t) that satisfies the above equation.

I cannot solve it using my elementary calculus knowledge, so I’ll cheat by asking Wolfram|Alpha to solve it for me.

But before we do that, let’s set a few constraints to make this less complicated.

Remember that before we let the block move, we pushed the block to the position x = 0. That’s the initial position, the position at time t = 0. Therefore, we have this:

We also know that before we let it go, the block doesn’t move. This means we also have this:

Unfortunately, as of writing, Wolfram|Alpha couldn’t solve this equation with k and c left as variables, so we need to put in some values.

I’m going to take the wobble preset from the react-motion library. It has a stiffness of k = 180, and the damping factor of c = 12.

Therefore, we end up with this set of equations:

And that’s what I asked Wolfram|Alpha:

And Wolfram|Alpha gave me this solution:

Where did ½, 6, sin and cos come from? I don’t know. ¯\_(ツ)_/¯

But it looks legit, as sin and cos feels like an oscillating motion, just like a spring. I’ll plot it on a graph from t = 0 to t = 1 by sampling the function’s value by 0.01. Here’s how it looks like.

It looks elastic, like a spring!

The CSS animation

You cannot put such a complex equation into CSS, but you can generate some CSS to closely approximate this equation. CSS animation only lets you specify discrete keyframes, but our equation is a continuous function.

Just like how I plotted the graph above, but instead of plotting into a graph, I will plot it into CSS keyframes. For example, if the animation will last 1 second, the CSS will look like this:

@keyframes keyframe-name {

0% { /* css code for t=0.00s */ }

1% { /* css code for t=0.01s */ }

2% { /* css code for t=0.02s */ }

3% { /* css code for t=0.03s */ }

/* ... */

99% { /* css code for t=0.99s */ }

100% { /* css code for t=1.00s */ }

}

Obviously, generating this by hand is not practical. We’re going to use some CSS preprocessing language to generate this. I’ll use Stylus.

First, I’ll write a Stylus function based on what Wolfram|Alpha told me:

spring-wobbly(t)

return -0.5 * (2.71828 ** (-6 * t)) * (

-2 * (2.71828 ** (6 * t)) + sin(12 * t) + 2 * cos(12 * t))

Also recall the interpolation function equation we covered in the tweening basics section. I’ll write a function to interpolate between two values:

lerp(a, b, p)

return a + p * (b - a)

Next, I’ll generate the keyframes.

@keyframes move

for i in (0..100)

{i + '%'}

t = i / 100

p = spring-wobbly(t)

left: lerp(100px, 200px, p)

Stylus generated this CSS:

@keyframes move {

0% { left: 100px; }

1% { left: 100.86376425736435px; }

2% { left: 103.30887625352196px; }

3% { left: 107.11510629593647px; }

4% { left: 112.06407562318391px; }

5% { left: 117.94274565577675px; }

6% { left: 124.54642206432976px; }

7% { left: 131.68127828753256px; }

8% { left: 139.16640928859013px; }

9% { left: 146.83543411253555px; }

10% { left: 154.53767168481676px; }

11% { left: 162.13891722725492px; }

12% { left: 169.5218524015242px; }

13% { left: 176.586122452165px; }

14% { left: 183.24811775766403px; }

15% { left: 189.44049645431824px; }

16% { left: 195.11148520727033px; }

17% { left: 200.22399583479304px; }

18% { left: 204.7545926401348px; }

19% { left: 208.6923456959847px; }

20% { left: 212.03760160865943px; }

21% { left: 214.80070272750575px; }

22% { left: 217.0006815538447px; }

23% { left: 218.663955706991px; }

24% { left: 219.82304538616665px; }

25% { left: 220.51533264040694px; }

26% { left: 220.78187825980012px; }

27% { left: 220.66631056510982px; }

28% { left: 220.21379606281764px; }

29% { left: 219.47010051916436px; }

30% { left: 218.48074615498618px; }

31% { left: 217.29026816681414px; }

32% { left: 215.94157202720172px; }

33% { left: 214.47539120167033px; }

34% { left: 212.92984275093278px; }

35% { left: 211.34007772340152px; }

36% { left: 209.7380212102259px; }

37% { left: 208.15219633458372px; }

38% { left: 206.60762598024857px; }

39% { left: 205.12580475213764px; }

40% { left: 203.72473387120573px; }

41% { left: 202.41901137453345px; }

42% { left: 201.2199697208098px; }

43% { left: 200.13585305311918px; }

44% { left: 199.17202686633095px; }

45% { left: 198.33121263498484px; }

46% { left: 197.61374067964204px; }

47% { left: 197.01781499211256px; }

48% { left: 196.53978407975245px; }

49% { left: 196.17441264256894px; }

50% { left: 195.91514930968282px; }

51% { left: 195.75438645693993px; }

52% { left: 195.68370845882814px; }

53% { left: 195.69412560592633px; }

54% { left: 195.7762912646242px; }

55% { left: 195.92070055484277px; }

56% { left: 196.1178692094388px; }

57% { left: 196.35849187467915px; }

58% { left: 196.63357948554946px; }

59% { left: 196.93457574055066px; }

60% { left: 197.25345307939384px; }

61% { left: 197.582788848219px; }

62% { left: 197.91582261394115px; }

63% { left: 198.24649577847353px; }

64% { left: 198.56947483257156px; }

65% { left: 198.880159678671px; }

66% { left: 199.17467862706735px; }

67% { left: 199.44987158683784px; }

68% { left: 199.70326312039px; }

69% { left: 199.93302695538216px; }

70% { left: 200.13794350123476px; }

71% { left: 200.31735190782865px; }

72% { left: 200.47109806939648px; }

73% { left: 200.5994799360452px; }

74% { left: 200.70319134949088px; }

75% { left: 200.78326552237604px; }

76% { left: 200.84101916476513px; }

77% { left: 200.87799812568187px; }

78% { left: 200.89592530095737px; }

79% { left: 200.89665143457395px; }

80% { left: 200.88210931137806px; }

81% { left: 200.85427175192046px; }

82% { left: 200.81511366810776px; }

83% { left: 200.76657838029143px; }

84% { left: 200.7105482768328px; }

85% { left: 200.64881982187876px; }

86% { left: 200.58308284617112px; }

87% { left: 200.51490398641354px; }

88% { left: 200.44571408112546px; }

89% { left: 200.37679929488283px; }

90% { left: 200.3092956925791px; }

91% { left: 200.24418697437903px; }

92% { left: 200.1823050421591px; }

93% { left: 200.12433307616453px; }

94% { left: 200.07081078240043px; }

95% { left: 200.02214147494948px; }

96% { left: 199.97860066859386px; }

97% { left: 199.940345864654px; }

98% { left: 199.90742722728143px; }

99% { left: 199.8797988721998px; }

100% { left: 199.8573305048547px; }

}

Finally, you just use it wherever you want. Make sure you set the timing function to linear, as we already precomputed the animation.