Skip to main content
Introducing packages.sweber.dev
Documentation menuEasing curves

Easing curves

Springs, the logistic S-curve and Bézier curves for JavaScript and CSS.

Every curve is an Easing: a function from 0..1 to progress, with a css property. String(easing) returns the CSS value, so it fits into template strings.

import { spring } from "@sweberdev/sigmoid/easing";

const pop = spring({ duration: 0.5, bounce: 0.3 });

pop(0.5);        // 1.037, past the target
pop.css;         // "linear(0, 0.0009 0.42%, …, 1)"
pop.duration;    // 819: natural duration in ms until it rests
el.style.transition = `transform ${pop.duration}ms ${pop}`;

@sweberdev/sigmoid/easing contains only the curves (about 1.3 kB), for projects that use another animation library.

Presets

NameCurve
ease.outcubic-bezier(0.16, 1, 0.3, 1), fast start and long landing; the default for reveals
ease.inOutcubic-bezier(0.65, 0, 0.35, 1)
ease.standardcubic-bezier(0.2, 0, 0, 1)
ease.sigmoidlogistic S-curve, steepness 10
ease.smoothspring without overshoot (perceived 0.5 s)
ease.bouncyspring with light overshoot (perceived 0.6 s)
ease.wobblyspring with a clear wobble (perceived 0.8 s)
ease.linearlinear

spring

spring({ duration: 0.6, bounce: 0.3 });               // perceptual, like SwiftUI
spring({ stiffness: 170, damping: 26, mass: 1 });     // physical
spring({ duration: 0.4, velocity: 4 });               // start with speed

The spring is solved analytically (under-, critically and overdamped) and ends when it stays within 0.1% of the target. In a scroll-driven animation the scroll range sets the length, the curve keeps its shape.

logistic

logistic(6);   // soft S
logistic(14);  // sharp S

Normalised so that it starts exactly at 0 and ends exactly at 1.

bezier, easing and toLinear

bezier(0.34, 1.56, 0.64, 1);               // same as cubic-bezier() in CSS
easing((t) => 1 - (1 - t) ** 4);           // any function, CSS generated
toLinear((t) => t * t, { tolerance: 0.001 }); // just the linear() string

toLinear samples the curve and removes every point that does not change the result by more than the tolerance (default 0.0015), so a spring needs 20 to 40 stops instead of hundreds. CSS linear() works in Chrome 113, Safari 17.2 and Firefox 112 and newer. Older browsers get the curve applied in JavaScript.

With Motion or GSAP

import { animate } from "motion";
animate(".box", { x: 200 }, { ease: ease.bouncy, duration: ease.bouncy.duration! / 1000 });

gsap.to(".box", { x: 200, ease: ease.wobbly, duration: 0.8 });

Custom CSS variables

import { cssVariables, spring } from "@sweberdev/sigmoid/easing";

const css = `:root { ${cssVariables({ brand: spring({ bounce: 0.2 }) }, "--ease-")} }`;
// :root { --ease-brand: linear(…); }