An open API service indexing awesome lists of open source software.

https://github.com/okikio/spring-easing

Quick and easy spring animation. Works with other animation libraries (gsap, animejs, framer motion, motion one, @okikio/animate, etc...) or the Web Animation API (WAAPI).
https://github.com/okikio/spring-easing

animation animejs easing framer-motion motion-one spring spring-easing typescript waapi web-animations-api

Last synced: 8 months ago
JSON representation

Quick and easy spring animation. Works with other animation libraries (gsap, animejs, framer motion, motion one, @okikio/animate, etc...) or the Web Animation API (WAAPI).

Awesome Lists containing this project

README

          

# spring-easing

[![Open Bundle](https://bundlejs.com/badge-light.svg)](https://bundlejs.com/?q=spring-easing&bundle "Check the total bundle size of spring-easing with whichever animation library you choose.")

[NPM](https://www.npmjs.com/package/spring-easing) | [Deno](https://deno.land/x/spring_easing) | [GitHub](https://github.com/okikio/spring-easing#readme) | [Docs](https://spring-easing.okikio.dev) | [License](#license)

Quick and easy spring animations. Works with other animation libraries ([gsap](https://greensock.com/), [animejs](https://animejs.com/), [@okikio/animate](http://npmjs.com/@okikio/animate), [motion one](https://motion.dev/), [framer motion](https://www.framer.com/docs/animation/), etc...) or the [Web Animation API (WAAPI)](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API/Using_the_Web_Animations_API), you can learn more in the [Usage](#use-with-animation-libraries) section.

`spring-easing` works by generating arrays of `frame`'s which when placed in linear order creates a smooth spring like animation.

> A `frame` represent a single frame of an animation

> _**Note**: the `spring-easing` package also supports 4 extra variants of [`spring`](https://spring-easing.okikio.dev/functions/SpringFrame), namely [`spring-in`](https://spring-easing.okikio.dev/functions/SpringInFrame), [`spring-out`](https://spring-easing.okikio.dev/functions/SpringOutFrame), [`spring-out-in`](https://spring-easing.okikio.dev/functions/SpringOutInFrame), and [`spring-in-out`](https://spring-easing.okikio.dev/functions/SpringInOutFrame), you can use these easing to create some really unique spring like animations._

You can create animation's like this with `spring-easing`,

A demo of the various spring-easings available

> _Check out the spring easing variants on [Codepen](https://codepen.io/okikio/pen/MWEMEgJ)._

> _**Attention**: This entire library is a lightweight version of the [`CustomEasing`](https://native.okikio.dev/animate/api/custom-easing/) implemented in [`@okikio/animate`](https://native.okikio.dev/animate), which supports only string and number interpolation. If you'd like the complete `CustomEasing` with color interpolation, complex value interpolation, and more, go through the source code as a [Github Gist](https://gist.github.com/okikio/bed53ed621cb7f60e9a8b1ef92897471#file-custom-easing-ts), which is licensed under the MIT license._

## Installation

### Node

```bash
npm install spring-easing
```

Others

```bash
yarn add spring-easing
```

or

```bash
pnpm install spring-easing
```

### Deno

```ts
import { SpringEasing } from "https://deno.land/x/spring_easing/mod.ts";
```

## Usage

```ts
import { SpringEasing } from "spring-easing";
// or
import SpringEasing from "spring-easing";
```

You can also use it directly through a script tag:

```html

// You can then use it like this
const { SpringEasing } = window.SpringEasing;

```

You can also use it via a CDN, e.g.

```ts
import { SpringEasing } from "https://esm.run/spring-easing";
// or
import { SpringEasing } from "https://esm.sh/spring-easing";
// or
import { SpringEasing } from "https://unpkg.com/spring-easing";
// or
import { SpringEasing } from "https://cdn.skypack.dev/spring-easing";
// or
import { SpringEasing } from "https://deno.bundlejs.com/file?q=spring-easing";
// or any number of other CDN's
```

### Use with Animation Libraries

> _**Note:** I cannot guarantee that every animation library works with `spring-easing`, for example, if an animation library doesn't support array values as keyframes, it won't work well with `spring-easing`._

The libraries that have been tested are:

| Animation Library | Support | Demo |
| ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ |
| [GSAP](https://greensock.com/) | ✅ Yes - [Wrap Method]() | [Codepen](https://codepen.io/okikio/pen/MWEMEgJ) |
| [animejs](https://animejs.com) | ✅ Yes - [Array Keyframes](https://animejs.com/documentation/#animationKeyframes) | [Codepen](https://codepen.io/okikio/pen/MWEMEgJ) |
| [Framer Motion](https://www.framer.com/motion/) | ✅ Yes - [Array Keyframes](https://www.framer.com/docs/animation/##keyframes) | [Codepen](https://codepen.io/okikio/pen/MWEMEgJ) |
| [Motion One](https://motion.dev) | ✅ Yes - [Array Keyframes](https://motion.dev/dom/animate#keyframes) | [Codepen](https://codepen.io/okikio/pen/MWEMEgJ) |
| [@okikio/animate](https://okikio.github.io/native/packages/animate) | ✅ Yes - [Array Keyframes](https://okikio.github.io/native/packages/animate/#animations) | [Codepen](https://codepen.io/okikio/pen/MWEMEgJ) |
| [Web Animation API (WAAPI)](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API) | ✅ Yes - [Array Keyframes](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API/Keyframe_Formats#:~:text=An-,object,-containing%20key%2Dvalue) | [Codepen](https://codepen.io/okikio/pen/MWEMEgJ) |
| [Linear Easing (CSS)](https://linear-easing-generator.netlify.app) | ✅ Yes - [CSS Spring Easing](#CSSSpringEasing) | [Codepen](https://codepen.io/okikio/pen/MWEMEgJ) |
| [Linear Easing (WAAPI)](https://linear-easing-generator.netlify.app) | ✅ Yes - [CSS Spring Easing](#CSSSpringEasing) | [Codepen](https://codepen.io/okikio/pen/MWEMEgJ) |

e.g.

```ts
import anime from "animejs";
import { SpringEasing, SpringOutFrame } from "spring-easing";

// Note: this is the return value of `SpringEasing` and `GenerateSpringFrames`
let [translateX, duration] = SpringEasing([0, 250], {
easing: "spring-out-in(1, 100, 10, 0)",
// You can change the size of Array for the SpringEasing function to generate
numPoints: 200,
// The number of decimal places to round, final values in the generated Array
// This option doesn't exist on `GenerateSpringFrames`
decimal: 5,
});

anime({
targets: "div",

// Using spring easing animate from [0 to 250] using `spring-out-in`
translateX,

// You can interpolate between strings
// You can set the easing without an easing options object
// You can interpolate between more than 2 values
// Remember the `0` index of `SpringEasing` is an array of spring animation keyframes
rotate: SpringEasing(
["0turn", 1, 0, 0.5],
[SpringOutFrame, 1, 100, 10, 0]
)[0],

// TIP... Use linear easing for the proper springy effect
easing: "linear",

// The optimal duration for this specific spring configuration, e.g. mass, velocity, damping, etc...
duration,
});
```

Or

```ts
import { CSSSpringEasing } from "spring-easing";

let [easings, duration] = CSSSpringEasing({
easing: "spring-out-in(1, 100, 10, 0)",
// The number of decimal places to round to
decimal: 5,
});

document.querySelector('div').animate({
// Using spring easing animate from [0 to 250] using `spring-out-in`
translate: ["0", "250px"],

// You can interpolate between strings
// You can set the easing without an easing options object
// You can interpolate between more than 2 values
// Remember the `0` index of `SpringEasing` is an array of spring animation keyframes
rotate: ["0turn", "1turn", "0turn", "0.5turn"],
}, {
// TIP... Use linear easing for the proper springy effect
easing: `linear(${easings})`,

// The optimal duration for this specific spring configuration, e.g. mass, velocity, damping, etc...
duration,
});
```

> _**Note**: make sure to read the comments above, as they are valuable resources for understanding what is happening._

> Check out this demo on [Codepen](https://codepen.io/okikio/pen/MWEdzNg)

## Showcase

A couple sites/projects that use `spring-easing`:

- [`postcss-spring-easing`](https://github.com/okikio/postcss-spring-easing)
- Your site/project here...

## API

What's New...

> **`NEW`** CSS Spring Easing & support for the `linear()` easing function
>
> ### CSSSpringEasing
>
> Generates a string that represents a set of values used with the linear-easing function to replicate spring animations,
> you can check out the linear-easing playground here https://linear-easing-generator.netlify.app/
> Or check out a demo on Codepen https://codepen.io/okikio/pen/vYVaEXM
>
> CSS Spring Easing has 4 properties they are `easing` (all spring frame functions are supported), `numPoints` (the size of the Array the frmae function should create), `decimal` (the number of decimal places of the values within said Array) and `quality` (how detailed/smooth the spring easing should be)..
>
> | Properties | Default Value |
> | ----------- | ----------------------- |
> | `easing` | `spring(1, 100, 10, 0)` |
> | `numPoints` | `50` |
> | `decimal` | `3` |
> | `quality` | `0.85` |
>
> `CSSSpringEasing` is meant to be used with the `linear-easing()` function to replicate spring animations.
> It is based on the work done by [Jake Archibald](https://github.com/jakearchibald) in his [Linear Easing Generator](https://github.com/jakearchibald/linear-easing-generator).
>
> > **Note**: This feature will only work on versions of browsers from ~a month ago (`Chrome & Edge 113`, and `Firefox 112`) except for `Safari` which doesn't support it yet.
>
> ```ts
> import { CSSSpringEasing } from "spring-easing";
>
> let [easing, duration] = CSSSpringEasing({
> easing: "spring-out-in(1, 100, 10, 0)",
>
> // You can change the size of Array for the SpringEasing function to generate
> numPoints: 200,
>
> // The number of decimal places to round, final values in the generated Array
> // This option doesn't exist on {@link GenerateSpringFrames}
> decimal: 5,
>
> // How detailed/smooth the spring easing should be
> // 0 means not smooth at all (shorter easing string)
> // 1 means as smooth as possible (this means the resulting easing will be a longer string)
> quality: 0.85,
> });
>
> document.querySelector("div").animate(
> {
> translate: ["0px", "250px"],
> rotate: ["0turn", "1turn", "0turn", "0.5turn"],
> },
> {
> easing: `linear(${easing})`,
>
> // The optimal duration for this specific spring
> duration,
> }
> );
> ```
>
> > **Note**: You can also use custom easings with `CSSSpringEasing` e.g.
>
> ```ts
> import {
> CSSSpringEasing,
> limit,
> registerEasingFunctions,
> } from "spring-easing";
>
> registerEasingFunctions({
> bounce: (t) => {
> let pow2: number,
> b = 4;
> while (t < ((pow2 = Math.pow(2, --b)) - 1) / 11) {}
> return (
> 1 / Math.pow(4, 3 - b) - 7.5625 * Math.pow((pow2 * 3 - 2) / 22 - t, 2)
> );
> },
> elastic: (t, params: number[] = []) => {
> let [amplitude = 1, period = 0.5] = params;
> const a = limit(amplitude, 1, 10);
> const p = limit(period, 0.1, 2);
> if (t === 0 || t === 1) return t;
> return (
> -a *
> Math.pow(2, 10 * (t - 1)) *
> Math.sin(
> ((t - 1 - (p / (Math.PI * 2)) * Math.asin(1 / a)) * (Math.PI * 2)) / p
> )
> );
> },
> });
>
> CSSSpringEasing("bounce"); // ["0, 0.013, 0.015, 0.006 8.1%, 0.046 13.5%, 0.06, 0.062, 0.054, 0.034, 0.003 27%, 0.122, 0.206 37.8%, 0.232, 0.246, 0.25, 0.242, 0.224, 0.194, 0.153 56.8%, 0.039 62.2%, 0.066 64.9%, 0.448 73%, 0.646, 0.801 83.8%, 0.862 86.5%, 0.95 91.9%, 0.978, 0.994, 1", ...]
> CSSSpringEasing("elastic(1, 0.5)"); // ["0, -0.005 32.4%, 0.006 40.5%, 0.034 51.4%, 0.033 56.8%, 0.022, 0.003, -0.026 64.9%, -0.185 75.7%, -0.204, -0.195, -0.146, -0.05, 0.1 89.2%, 1", ...]
> ```
>
> ### getOptimizedPoints
>
> This function generates an optimized set of points to be used with the `linear-easing()` function
> using the Ramer-Douglas-Peucker algorithm and rounds the x and y values of the resulting points.
>
> ```ts
> import { getOptimizedPoints } from "spring-easing";
>
> const points = [
> [0, 0],
> [0.1, 0.2],
> [0.5, 1],
> [0.9, 0.2],
> [1, 0],
> ];
> const round = 2;
> const simplify = 0.1;
>
> console.log(getOptimizedPoints(points, simplify, round)); //= [[0, 0], [0.5, 1], [1, 0]]
> ```
>
> ### getLinearSyntax
>
> This function converts a given set of points into an array of strings in a this format `["value percent%", ...]` e.g. `["0", "0.25 13.8%", "0.6 45.6%", "0.1", "0.4 60%", ...]`.
> It's used to generate the syntax for the `linear-easing` function.
>
> ```ts
> import { getLinearSyntax } from "spring-easing";
>
> const points = [
> [0, 0],
> [0.1, 0.2],
> [0.5, 1],
> [0.9, 0.2],
> [1, 0],
> ];
> const round = 2;
>
> console.log(getLinearSyntax(points, round)); //= [ '0', '0.2 10%', '1', '0.2 90%', '0' ]
> ```

> **`RE-INSTATED`** _Added batch version of `SpringEasing` and the Interpolation functions which use a new syntax._
>
> _The other version of `spring-easing` interpolation functions follow this syntax `(t, values, decimal) => string | number | any`, batch interpolation function use this new syntax `(arr_t, values, decimal) => string[] | number[] | any[]`._
>
> _The key difference between both syntaxes are the parameters each function takes and the return value of each function._
>
> _The older syntax returned instantaneous frame values at a specific t-value, but the new syntax returns all the frames that make the entire animation, allowing for performance optimizations that couldn't be done before._
>
> _For the most part this shouldn't leave too much of an effect, but for those high-perf. applications this new batch synatax should prove useful._
> e.g.
>
> ```ts
> function batchInterpolateNumber(
> arr_t: number[],
> values: number[],
> decimal = 3
> ) {
> // nth index
> const n = values.length - 1;
>
> return arr_t.map((t) => {
> // The current index given t
> const i = limit(Math.floor(t * n), 0, n - 1);
>
> const start = values[i];
> const end = values[i + 1];
> const progress = (t - i / n) * n;
>
> return toFixed(scale(progress, start, end), decimal);
> });
> }
>
> BatchSpringEasing([0, 250], `spring`, batchInterpolateNumber);
> ```

> **`RE-INSTATED`** _There is a new `toAnimationFrames` function that converts interpolation functions written in this style `(t, values, decimal) => { ... }` to work in `BatchSpringEasing`._
> e.g.
>
> ```ts
> import {
> BatchSpringEasing,
> toAnimationFrames,
> toFixed,
> scale,
> limit,
> } from "spring-easing";
>
> function interpolateNumber(t: number, values: number[], decimal = 3) {
> // nth index
> const n = values.length - 1;
>
> // The current index given t
> const i = limit(Math.floor(t * n), 0, n - 1);
>
> const start = values[i];
> const end = values[i + 1];
> const progress = (t - i / n) * n;
>
> return toFixed(scale(progress, start, end), decimal);
> }
>
> function interpolatePixels(t: number, values: number[], decimal = 3) {
> const result = interpolateNumber(t, values, decimal);
> return `${result}px`;
> }
>
> BatchSpringEasing([0, 250], "spring", toAnimationFrames(interpolatePixels));
> ```

> **`NEW`** _Optimized perf. of spring generation w/ help from [@jakearchibald](https://twitter.com/jaffathecake)_

> **`NEW`** _`mass`, `stiffness`, `damping`, and `velocity` now have a smaller minimum limit of `0.0001` instead of `0.1`_

> **`REVERT`** _The new interpolation syntax has been reverted and removed; `instantNumber`, etc... functions have been renamed to `interpolateNumber`, etc..._

> **`NEW`** _Re-introduced instantaneous interpolation functions._
> e.g.
>
> ```ts
> import {
> interpolateNumber,
> interpolateString,
> interpolateSequence,
> interpolateComplex,
> } from "spring-easing";
> ```
>
> These functions represent the interpolated value at a specific instance in time, where time is represented by `t` with a range of `0` to `1`.
> You can use these functions as building blocks to create your own custom interpolation functions.

> **`NEW`** _(deprecated) `interpolateUsingIndex` is now an alias of `interpolateSequence`, it still keeps the same functionality._
> The recommendation is to use `interpolateSequence` instead of `interpolateUsingIndex`, but you can still keep using `interpolateUsingIndex`, but beware it can be removed in future versions.

> **`NEW`** _Re-introduced instantaneous interpolation functions._
> e.g.
>
> ```ts
> import {
> instantNumber,
> instantString,
> instantSequence,
> instanceComplex,
> } from "spring-easing";
> ```
>
> These functions represent the interpolated value at a specific instance in time, where time is represented by `t` with a range of `0` to `1`.
> You can use these functions as building blocks to create your own custom interpolation functions.
>
>

> **`BREAKING CHANGE`** _Interpolation functions use a new syntax._
>
> _In older versions of `spring-easing` interpolation functions used to follow a syntax called the instantaneous interpolation function `(t, values, decimal) => string | number | any`, the new syntax is called interpolation function `(frames, values, decimal) => string[] | number[] | any[]`._
>
> _The key difference between both syntaxes are the parameters each function takes and the return value of each function._
>
> _The older syntax returned instantaneous frame values at a specific t-value, but the new syntax returns all the frames that make the entire animation, allowing for performance optimizations that couldn't be done before._
>
> _For the most part this shouldn't leave too much of an effect as all the built-in interpolation functions have been updated to use the new synatax._
> e.g.
>
> ```ts
> function interpolateNumber(frames: number[], values: number[], decimal = 3) {
> // nth index
> const n = values.length - 1;
>
> return frames.map((t) => {
> // The current index given t
> const i = limit(Math.floor(t * n), 0, n - 1);
>
> const start = values[i];
> const end = values[i + 1];
> const progress = (t - i / n) * n;
>
> return toFixed(scale(progress, start, end), decimal);
> });
> }
>
> SpringEasing([0, 250], `spring`, interpolateNumber);
> ```
>
>

> **`NEW`** _There is a new `toAnimationFrames` function that be used on instantaneous interpolation functions, to transform them into complete animation interpolation functions._
> e.g.
>
> ```ts
> import {
> SpringEasing,
> toAnimationFrames,
> toFixed,
> scale,
> limit,
> } from "spring-easing";
>
> function interpolateNumber(t: number, values: number[], decimal = 3) {
> // nth index
> const n = values.length - 1;
>
> // The current index given t
> const i = limit(Math.floor(t * n), 0, n - 1);
>
> const start = values[i];
> const end = values[i + 1];
> const progress = (t - i / n) * n;
>
> return toFixed(scale(progress, start, end), decimal);
> }
>
> function interpolatePixels(t: number, values: number[], decimal = 3) {
> const result = interpolateNumber(t, values, decimal);
> return `${result}px`;
> }
>
> SpringEasing([0, 250], "spring", toAnimationFrames(interpolatePixels));
> ```
>
>

> **`NEW`** _Easily register new easing functions._
> e.g.
>
> ```ts
> import { SpringEasing, registerEasingFunction } from "spring-easing";
>
> registerEasingFunction("linear", (t) => t);
> registerEasingFunctions({
> quad: (t) => Math.pow(t, 2),
> cubic: (t) => Math.pow(t, 3),
> });
>
> SpringEasing([0, 250], "linear");
>
> SpringEasing([0, 250], "quad");
> ```

> **`NEW`** _SpringEasing now support interpolating between strings. It treats the units of the first value as the units for the rest of the values to interpolate between._
> e.g.
>
> ```ts
> SpringEasing(["0turn", "1px", "18rem", "125deg", 25], ...)
> ```
>
> _**Important** All the values above get transformed to `["0turn", "1turn", "18turn", "125turn", "25turn"]`, before being interpolated._

> **`NEW`** _`interpolateStrings`, `interpolateUsingIndex`, and `interpolateComplex`, are now built-in, they allow for supporting string keyframes._

> **`NEW`** _Custom interpolation functions are now supported._
> e.g.
>
> ```ts
> import { interpolateNumber, toFixed, scale, limit } from "spring-easing";
> // ...
> export function interpolateColor(t: number, values: string[], decimal = 3) {
> const color = transpose(...values.map((v) => rgba(v))).map(
> (colors: number[], i) => {
> const result = interpolateNumber(t, colors);
> return i < 3 ? Math.round(result) : toFixed(result, decimal);
> }
> );
>
> return `rgba(${color.join()})`;
> }
>
> SpringEasing(["red", "green", "#4f4"], "spring", interpolateColor);
> ```
>
> _**Important** The logic for color interpolation is defined in this [tests/utils/interpolate-color.ts](https://github.com/okikio/spring-easing/blob/main/tests/utils/interpolate-color.ts)._

The API of `spring-easing` is pretty straight forward, the `SpringEasing` function generates an array of values using a frame functions, which in turn creates the effect of spring easing.

To use this properly make sure to set the easing animation option to "linear".
Check out a demo of `SpringEasing` at

`SpringEasing` has 3 properties they are `easing` (all the easings from [EasingFunctions](https://spring-easing.okikio.dev/variables/easingfunctions) are supported on top of frame functions like `SpringFrame`, `SpringFrameOut`, etc..), `numPoints` (the size of the Array the frame function should create), and `decimal` (the number of decimal places of the values within said Array).

| Properties | Default Value |
| ----------- | ----------------------- |
| `easing` | `spring(1, 100, 10, 0)` |
| `numPoints` | `50` |
| `decimal` | `3` |

By default, Spring Easing support easings in the form,

| constant | accelerate | decelerate | accelerate-decelerate | decelerate-accelerate |
| :------- | :----------------- | :--------- | :-------------------- | :-------------------- |
| | spring / spring-in | spring-out | spring-in-out | spring-out-in |

All **Spring** easing's can be configured using theses parameters,

`spring-*(mass, stiffness, damping, velocity)`

Each parameter comes with these defaults

| Parameter | Default Value |
| --------- | ------------- |
| mass | `1` |
| stiffness | `100` |
| damping | `10` |
| velocity | `0` |

To understand what each of the parameters of `SpringEasing` mean and how they work I suggest looking through the [SpringEasing API Documentation](https://spring-easing.okikio.dev/functions/springeasing)

> _**Note:** the return value of the `SpringEasing` function is actually `[Array of keyframes, duration]`, in that order._

For a full understanding of what is happening in the library, pleace check out the [API site](https://spring-easing.okikio.dev/modules) for detailed API documentation.

## Browser Support

| Chrome | Edge | Firefox | Safari | IE |
| ------ | ---- | ------- | ------ | --- |
| 4+ | 12+ | 4+ | 4+ | 10+ |

Native support for `spring-easing` is great as it doesn't use any browser specific or nodejs specific API's, you should be good to use `spring-easing` in any environment.

> Note: `CSSSpringEasing` is meant for browsers which have support for the `linear()` easing function,
> which as of right now is `Chrome & Edge 113` + `Firefox 112`, Safari doesn't support it yet.

## Contributing

I encourage you to use [pnpm](https://pnpm.io/configuring) to contribute to this repo, but you can also use [yarn](https://classic.yarnpkg.com/lang/en/) or [npm](https://npmjs.com) if you prefer.

Install all necessary packages

```bash
npm install
```

Then run tests

```bash
npm test
```

Build project

```bash
npm run build
```

Preview API Docs

```bash
npm run typedoc && npm run preview
```

> _**Note**: this project uses [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) standard for commits, so, please format your commits using the rules it sets out._

## License

See the [LICENSE](./LICENSE) file for license rights and limitations (MIT).

The `CSSSpringEasing`, `getOptimizedPoints` and `getLinearSyntax` function are based of the work done by [Jake Archibald](https://github.com/jakearchibald/linear-easing-generator) in his [Linear Easing Generator](https://linear-easing-generator.netlify.app/)
and are thus licensed under the [Apache License 2.0](https://github.com/jakearchibald/linear-easing-generator/blob/main/LICENSE).