Easing
- API Reference Easings module
- fn-vis: useful for seeing output values
- Online modulation demos
Easing functions help to give a dynamic to transition. In Cartesian terms, they give a y value for x (where x is 0 .. 1). Or in temporal terms, you can think of them as giving a value at time t (where t is 0 .. 1).
Normally, a way of getting from 0 to 1 would be count upwards by some fixed amount. And if that's all you need, ixfx's count and numericRange functions might do the job.
For example, count by 0.1 from 0 to 1:
This is a linear function, with the same 'speed' throughout the progression towards the end point.
In contrast, easing functions give some dynamics to the journey. For example, maybe it starts slowly, but gets faster as it nears the end, as in this cubicIn
function:
Or perhaps gathering speed quickly, but then slowing down toward the end (cubicOut
):
Jump to the Defined Easings to see a list of pre-defined easing functions.
Usage
Easings can be driven by time or ticks, created by Easings.time
or Easings.tick
, respectively.
Each of these returns an Easings.Easing
, which has this type:
type Easing = {
// Returns value at this point
// Usually 0-1, but some functions can overshoot
compute():number
// Reset easing to beginning
reset():void
// Returns true if easing is finished
get isDone():boolean
}
Time-based
Example: 'sineIn' easing that takes one second to complete:
import { Easings } from "https://unpkg.com/ixfx/dist/modulation.js";
// Initialise
const e = Easings.time(`sineIn`, 1000);
// Returns value at this point in time.
// Usually 0-1, but some functions overshoot bounds
const v = e.compute();
// ..call e.compute() whenever the 'latest' value is needed
// Can check if easing has completed
if (e.isDone) ...
Time starts being counted from when the easing is initialised (ie. calling Easings.time()
). For this reason, you likely want to initialise just before it's needed, or values you will lose part of the range. Calling reset()
can also be useful for resetting the timer.
You'll probably call compute()
inside an existing draw/update loop that is running. If you don't have a loop already, here is a snippet that returns an easing over time:
import { Generators, Flow } from "https://unpkg.com/ixfx/dist/bundle.js";
import { Easings } from "https://unpkg.com/ixfx/dist/modulation.js";
// Define an easing that takes 1 second to reach end
const e = Easings.time(`sineIn`, 1000);
// Increment by 10% from 0 to 1
const range = Generators.numericPercent(0.1);
// Iterate over range at 100ms intervals
for await (const v of Flow.interval(range, 100)) {
console.log(e.compute());
}
Tick-based
Example: a sineOut
easing that takes 100 ticks to complete:
import { Easings } from "https://unpkg.com/ixfx/dist/modulation.js";
// Set up
const e = Easings.tick(`sineOut`, 100);
// Get the value of the easing function at this point
const v = e.compute();
// ...each call to compute() will 'advance' the easing
// function by one tick. So after 100 calls to compute()
// the function will be done.
// Can check if easing has completed
if (e.isDone) ...
compute
typically returns value between 0..1, but some functions purposefully overshoot this range (such as the back
series of easings, shown below).
Once you have this value, it can be applied as necessary. For example, positioning an element:
el.style.transform = `translate(${e.compute() * width}px, 0px)`;
See the source of the demos below for more ideas, or try it out on fn-vis
It's a function
It's good to remember that easing functions are not magic. They just return a number based on an input within the range of 0..1.
An easing-function-from-scratch example is an exponential function:
const fn = (x) => Math.pow(x,2);
fn(0); // 0
fn(0.5); // 0.25
fn(0.75); // 0.5625
fn(1); // 1
You can see this in action:
Try the following functions:
x
: Linear, returns the same input valuex/2
: Halves input valueMath.sqrt(x)
: Square root of valueMath.random() * x
: Reduces value by some random amountx + (0.1 - Math.random()* 0.2)
: Jitters value by up to 10%
Pre-defined easing functions can be used directly to avoid the time/tick mechanism. In this case, you need to provide an input value on a scale of 0..1.
import { Easings } from "https://unpkg.com/ixfx/dist/modulation.js";
// Get the function
const fn = Easings.functions.cubicIn;
// Use it to transform an input value (0..1)
// and set to variable 'v'
const v = fn(0.5);
Demos
Here, the easing function advances on each call (tapping the circle), rather than by time.
Below is the common usage of time-based easing
In this demo, a target value is reached over time by using an easing function.
Custom curves
Simple cubic beziers
You can make your own easing curve using a simplified cubic bezier. See this curve, for example. It's defined by points 0, 1.24, 1, -1.15
. The key points we need here are 1.24
(point b), and -1.15
(point d).
This can be used as follows:
import { Easings } from "https://unpkg.com/ixfx/dist/modulation.js";
const e = Easings.time(Easings.fromCubicBezier(1.24, -1.15), 1000);
e.compute();
Use the cubic bezier editor to shape a curve, but make sure the first point remains 0 and the third point remains 1.
Defined easings
There are several well known easing functions which are pre-defined in ixfx.
Credits
Most easing functions by Andrey Sitnik and Ivan Solovev