Skip to main content

Easing functions

easing

Commentary

added in 1.5.0

stateful function

Applies an easing function to change a number over time. This is useful if you want to simulate a value ramping up or down at particular velocities, especially mathematically complex ones.

Like many things, easings—which are common in animation APIs—seem complicated at first, but become simple when explained with an example.

Imagine that you want to generate some data that slowly moves a value from 5 to 50. How would you do that?

One idea you might have is to use sequentialInteger to advance the number one data point at a time. That works great for the simplest case, but what if you want to advance towards 50 at a nonlinear rate? Perhaps intervals could do the job? But that doesn't work either because the numbers it produces will change at abrupt pitches. At one interval, your value might be 5, and at the next, 15, and so on.

What you want is some way to glide your value between 5 and 50 and see intermediate values between the two, along with the option to control the rate it advances.

Easings solve that problem.


To use them, you specify a handful of parameters. The simplest ones to understand are from (the value you want to start at) and to (the value you want to end at).

In addition, you also specify how long the easing should take. You can do this in terms of time (duration) or in terms of data (events). If you choose duration, ShadowTraffic will use the wallclock to decide when to stop transitioning the value and emit as many events during that time as you allow it. 1 If you choose events, ShadowTraffic will simply scale the from to the to value over the number of events you specified. 2

In either case, when the easing function reaches to value, it will only ever produce that value thereafter. 3

In addition to defining those parameters, you also define a direction (the shape of the line between your two values) and degree (the steepness of the line). These parameters follow well-known mathematical formulas. For example, direction 4 can be:

  • easeIn: the transition starts slowly, and then accelerates
  • easeOut: the transition starts fast, and then decelerates
  • easeInOut: the transition is slow at both ends, but fast in the middle

Likewise, degree 5 has like familiar values:

  • linear: a constant rate of transition, t
  • quad: a quadratic rate of transition, t^2
  • cubic: a cubic rate of transition, t^3
  • quart: a quartic rate of transition, t^4
  • quint: a quintic rate of transition, t^5

When you specify an easing, you combine a direction and a degree into a pair to create transitions like easeIn / linear, or easeInOut / cubic. These pairings specify the precise slope of the line between your from and to values .

If you have trouble visualizing what any of these look like, many of these pairs are helpfully graphed on Easings.net

Lastly, in addition to the predefined easing direction / degree pairs, you may also supply your own numeric function. 6 This is especially useful if you want to stack easings with easingChain and peg a value at a particular number.


Examples

Linear easing

Linear easings are the most simple example. They consistently advance from to to at a constant rate. When you use a linear degree, the choice of direction doesn't matter because its rate never deviates.

In this example, ShadowTraffic will emit events for 8000 milliseconds that transition 5 to 125.

{
"_gen": "easing",
"direction": "easeIn",
"degree": "linear",
"from": 5,
"to": 125,
"duration": 8000
}
[
5,
5.015001875234404,
5.030003750468809,
5.0450056257032125,
5.060007500937617,
5.075009376172021,
5.090011251406426,
5.10501312664083,
5.120015001875235,
5.1350168771096385
]

These values resemble the plot:

Counting easings

In addition to specifying easing lengths as a duration, you can also specify them in terms of the number of events. In this example, the easing will take 5 events to complete. This is particularly useful if you're generating static, batch data sets.

{
"_gen": "easing",
"direction": "easeInOut",
"degree": "linear",
"from": 3,
"to": 12,
"events": 5
}
[
3,
5.25,
7.5,
9.75,
12
]

Capping the end value

When the duration is reached, easing will peg the result to the value of from. In this example, duration is set to 5 milliseconds, a very small amount of time. easing generates a few intermediate values, and all values after will be 12.

{
"_gen": "easing",
"direction": "easeIn",
"degree": "linear",
"from": 10,
"to": 12,
"duration": 5
}
[
10,
10.5,
11,
11.5,
12,
12,
12,
12,
12,
12
]

Which resembles the plot:

Changing direction

Use a different direction to change the slope of the tails of the curve. In this example, easeOut will decelerate the rate of change as the value gets closer to 70.

{
"_gen": "easing",
"direction": "easeOut",
"degree": "cubic",
"from": 50,
"to": 70,
"duration": 10000
}
[
50,
50.00599999996,
50.01199879979998,
50.01799639964002,
50.02399279960012,
50.02998799980033,
50.03598200036068,
50.04197480140122,
50.04796640304197,
50.053956805402976
]

Plotting the values, you can see how easeOut has an accelerated start, but "easy / flat" ending:

Changing degree

Likewise, use a different degree to change the steepness of the curve. Higher polynomials (like quart) have steeper curves than lower polynomials (like quad).

{
"_gen": "easing",
"direction": "easeInOut",
"degree": "quart",
"from": 100,
"to": 30,
"duration": 3000
}
[
100,
99.99999999999308,
99.99999999988924,
99.99999999943925,
99.99999999822776,
99.99999999567325,
99.99999999102805,
99.99999998337834,
99.99999997164419,
99.99999995457947
]

Plotting the values, you can see the curve with flat tails at both ends:

Custom easings

If you want to define your own easing, set ease to any numeric function and duration.

{
"_gen": "easing",
"direction": "easeInOut",
"ease": {
"_gen": "normalDistribution",
"mean": 100,
"sd": 5
},
"duration": 5000
}
[
105.46946550894056,
100.23162027249977,
94.57096414497,
95.13027563999286,
97.17970044868987,
99.0733263034791,
104.18051750224168,
94.17191163932868,
103.30950041849384,
101.89600287575489
]

Imperfect lines

If the mathematically perfect curves that easing creates aren't realistic, you can use math to add a small amount of "jitter" to each value. Notice how the values aren't strictly ascending anymore.

{
"_gen": "math",
"expr": "value + jitter",
"names": {
"value": {
"_gen": "easing",
"direction": "easeIn",
"degree": "quad",
"from": 100,
"to": 150,
"duration": 5000
},
"jitter": {
"_gen": "uniformDistribution",
"bounds": [
-1,
1
]
}
}
}
[
99.14409961507134,
99.28048518603984,
99.12853934250555,
100.51843732984679,
100.3682929870021,
100.86177129197452,
99.55407231399175,
100.53739405633668,
100.00236942234422,
99.33033395318904
]

Specification

JSON schema

{
"oneOf": [
{
"type": "object",
"properties": {
"direction": {
"type": "string",
"enum": [
"easeIn",
"easeOut",
"easeInOut"
]
},
"degree": {
"type": "string",
"enum": [
"linear",
"quad",
"cubic",
"quart",
"quint"
]
},
"from": {
"oneOf": [
{
"type": "number"
},
{
"type": "object",
"properties": {
"_gen": {
"type": "string"
}
},
"required": [
"_gen"
]
}
]
},
"to": {
"oneOf": [
{
"type": "number"
},
{
"type": "object",
"properties": {
"_gen": {
"type": "string"
}
},
"required": [
"_gen"
]
}
]
},
"duration": {
"oneOf": [
{
"type": "integer",
"minimum": 1
},
{
"type": "object",
"properties": {
"_gen": {
"type": "string"
}
},
"required": [
"_gen"
]
}
]
},
"events": {
"oneOf": [
{
"type": "integer",
"minimum": 1
},
{
"type": "object",
"properties": {
"_gen": {
"type": "string"
}
},
"required": [
"_gen"
]
}
]
}
},
"anyOf": [
{
"required": [
"direction",
"degree",
"from",
"to",
"duration"
]
},
{
"required": [
"direction",
"degree",
"from",
"to",
"events"
]
}
]
},
{
"type": "object",
"properties": {
"ease": {
"oneOf": [
{
"type": "number"
},
{
"type": "object",
"properties": {
"_gen": {
"type": "string"
}
},
"required": [
"_gen"
]
}
]
},
"duration": {
"oneOf": [
{
"type": "integer",
"minimum": 1
},
{
"type": "object",
"properties": {
"_gen": {
"type": "string"
}
},
"required": [
"_gen"
]
}
]
},
"events": {
"oneOf": [
{
"type": "integer",
"minimum": 1
},
{
"type": "object",
"properties": {
"_gen": {
"type": "string"
}
},
"required": [
"_gen"
]
}
]
}
},
"anyOf": [
{
"required": [
"ease",
"duration"
]
},
{
"required": [
"ease",
"events"
]
}
]
}
]
}