Skip to content

robertpenner/easing

Repository files navigation

Mirror of a package developed in a private monorepo — issues and pull requests are welcome here.

@penner/easing

Modern TypeScript implementations of the classic Penner easing functions with physics-based parameters and tree-shakeable exports.

npm version

Features

  • Tree-shakeable: Import only the easing functions you need
  • Physics-based: Configure easings with intuitive physical parameters
  • TypeScript: Full type safety with comprehensive interfaces
  • Modern API: Clean factory functions with sensible defaults
  • High performance: Optimized implementations with numerical stability

Installation

npm install @penner/easing

Quick Start

import { back, bounce, physicsSpring } from "@penner/easing";

// Each family is callable with a config and returns { in, out, inOut, outIn }
const bounceOut = bounce({ bounces: 4, decay: 0.95 }).out;
const backOut = back({ overshoot: 0.15 }).out;
const springOut = physicsSpring({ bounces: 4, decay: 0.9 }).out;

// Defaults are also available as properties — no call needed
const defaultBackOut = back.out;

// Use in animations
const progress = bounceOut(0.5); // Returns the eased value at t=0.5

Easing Families

Power

Custom polynomial easings with any exponent, including fractional powers.

import { power } from "@penner/easing";

// Create custom power families
const sqrt = power({ power: 0.5 }); // Square root easing
const custom = power({ power: 2.5 }); // t^2.5 easing

const sqrtOut = sqrt.out;
const customIn = custom.in;

// All variants
const fam = power({ power: 1.7 });
const easeIn = fam.in;
const easeOut = fam.out;
const easeInOut = fam.inOut;
const easeOutIn = fam.outIn;

Flex

Unified family that combines Power's exponent with Back's anticipation. For exponent > 1, the curve dips below 0 (anticipation) before accelerating to 1 — generalizing classic cubic Back to any power.

import { flex } from "@penner/easing";

// Defaults (power: 3, overshoot: 0.1) — equivalent to classic Back
const easeIn = flex.in;
const easeOut = flex.out;

// Custom: stronger anticipation with a steeper acceleration
const snappy = flex({ power: 4, overshoot: 0.2 });
const snappyOut = snappy.out;
const snappyInOut = snappy.inOut;
interface FlexConfig {
  power: number; // Power exponent (> 1 for overshoot; default 3)
  overshoot: number; // Anticipation depth as a fraction (default 0.1)
}

Setting overshoot: 0 collapses to pure power easing (u^n); power: 3 with a custom overshoot matches classic back.

Back

Creates overshoot effects where the animation goes beyond its target before settling.

import { back } from "@penner/easing";

// Default overshoot (10%) — use the family's default variants directly
const easeOut = back.out;

// Custom overshoot (20%)
const easeOutBig = back({ overshoot: 0.2 }).out;

// All variants
const easeIn = back({ overshoot: 0.15 }).in;
const easeInOut = back({ overshoot: 0.1 }).inOut;
const easeOutIn = back({ overshoot: 0.1 }).outIn;

Bounce

Physics-based bouncing with configurable energy loss and number of bounces.

import { bounce } from "@penner/easing";

// Default bounce (4 bounces, 95% decay)
const easeOut = bounce.out;

// Custom bounce
const easeOutBouncy = bounce({
  bounces: 6,
  decay: 0.8,
}).out;

// All variants
const easeIn = bounce({ bounces: 3, decay: 0.9 }).in;
const easeInOut = bounce.inOut;
const easeOutIn = bounce.outIn;

OverBounce

Bounce easing that overshoots above the target before settling through decaying parabolic bounces.

import { overBounce } from "@penner/easing";

// Default: 0.3 overshoot, 3 bounces, 60% decay
const easeOut = overBounce.out;

// Higher overshoot
const easeOutBig = overBounce({ overshoot: 0.5, bounces: 4 }).out;

// Specify initial velocity instead of overshoot
const easeOutFast = overBounce({ v0: 4.0, bounces: 2 }).out;

// All variants
const easeIn = overBounce.in;
const easeInOut = overBounce.inOut;
const easeOutIn = overBounce.outIn;

Spring

Damped oscillations with configurable bounces and decay.

import { physicsSpring } from "@penner/easing";

// Default spring (4 bounces, 95% decay)
const easeOut = physicsSpring.out;

// Custom oscillations
const easeOutCustom = physicsSpring({
  bounces: 6,
  decay: 0.9,
}).out;

// Critically damped (no oscillation)
const easeOutSmooth = physicsSpring({ bounces: 0 }).out;

// All variants
const easeIn = physicsSpring.in;
const easeInOut = physicsSpring.inOut;
const easeOutIn = physicsSpring.outIn;

Overdamped Spring

Overdamped spring easing for smooth, non-oscillating motion. Single-knob API with a normalized [0, 1] overdamping parameter.

import { overdampedSpring } from "@penner/easing";

// Default overdamped spring (overdamping = 0.5, ζ = 2)
const easeOut = overdampedSpring.out;

// Sharper arrival (closer to critical damping)
const sharp = overdampedSpring({ overdamping: 0.2 }).out;

// Languid, near-linear approach
const heavy = overdampedSpring({ overdamping: 0.85 }).out;

// Endpoint delegations:
const critical = overdampedSpring({ overdamping: 0 }).out; // critically damped
const linear = overdampedSpring({ overdamping: 1 }).out; // linear easing

// All variants
const easeIn = overdampedSpring.in;
const easeInOut = overdampedSpring.inOut;
const easeOutIn = overdampedSpring.outIn;

overdamping maps to the damping ratio ζ via soft saturation: ζ = 1 / (1 − overdamping). For exact damping-ratio control or cross-tool spring-physics compatibility, use physicsSpring({ bounces: 0, dampingRatio }) (planned).

InnerSpring

Spring easing that oscillates below the target but never exceeds 1. Useful for animations where overshoot is undesirable (e.g., opacity, corner radius).

import { innerSpring } from "@penner/easing";

// Default (1 bounce, 95% decay)
const easeOut = innerSpring.out;

// More oscillations
const easeOutBouncy = innerSpring({ bounces: 3 }).out;

// Custom decay
const easeOutCustom = innerSpring({ bounces: 2, decay: 0.5 }).out;

// All variants
const easeIn = innerSpring.in;
const easeInOut = innerSpring.inOut;
const easeOutIn = innerSpring.outIn;

OuterSpring

Spring easing that overshoots above the target and oscillates back down to settle at 1. After the initial cruise phase, the curve always stays at or above 1.

import { outerSpring } from "@penner/easing";

// Default (1 bounce, 95% decay)
const easeOut = outerSpring.out;

// More oscillations
const easeOutBouncy = outerSpring({ bounces: 3 }).out;

// Less aggressive decay
const easeOutCustom = outerSpring({ bounces: 2, decay: 0.5 }).out;

// All variants
const easeIn = outerSpring.in;
const easeInOut = outerSpring.inOut;
const easeOutIn = outerSpring.outIn;

Standard Easings

Classic polynomial and trigonometric easing functions. Each is a StandardEasingFamily with .in, .out, .inOut, and .outIn properties.

import { quadratic, cubic, quartic, quintic, sine, circular, exponential } from "@penner/easing";

// Access variants as properties (not function calls)
const quadOut = quadratic.out;
const cubicIn = cubic.in;
const quartInOut = quartic.inOut;
const sineOutIn = sine.outIn;

Superellipse

Generalizes circular easing via the Lamé curve $|x|^n + |y|^n = 1$. The exponent controls the curve shape: 1 = linear, 2 = circular, →∞ = square (step-like).

import { superellipse } from "@penner/easing";

// Circular (identical to the `circular` family)
const circ = superellipse({ power: 2 });

// Softer than circular
const soft = superellipse({ power: 1.5 });

// Sharper, more squared-off
const sharp = superellipse({ power: 4 });

// All variants
const easeIn = sharp.in;
const easeOut = sharp.out;
const easeInOut = sharp.inOut;
const easeOutIn = sharp.outIn;
interface SuperellipseConfig {
  /** Superellipse exponent (1 = linear, 2 = circular, →∞ = square) */
  power: number;
}

Linear, Smoothstep, Smootherstep, Smootheststep

Simple easing functions exported as single EasingFn values (not families).

import { linear, smoothstep, smootherstep, smootheststep } from "@penner/easing";

const value = smoothstep(0.5); // C¹ Hermite (degree 3)
const value2 = smootherstep(0.5); // C² (degree 5)
const value3 = smootheststep(0.5); // C³ (degree 7)

smoothstepN(n) is the generalized factory — returns the degree-(2n+1) polynomial with n vanishing derivatives at both endpoints:

import { smoothstepN } from "@penner/easing";

// smoothstepN(1) ≡ smoothstep, smoothstepN(2) ≡ smootherstep, smoothstepN(3) ≡ smootheststep
const custom = smoothstepN(4); // degree 9, C⁴ smooth

Exponential

Configurable exponential easing with utility functions.

import { exponential, makeExponentialEaseOut } from "@penner/easing";

// Standard exponential family
const easeOut = exponential.out;
const easeIn = exponential.in;

// Custom exponential ease-out
const customExpo = makeExponentialEaseOut(10);

Force-Based Easings (Experimental)

Physics-based easing functions derived from force models. These are experimental and their APIs may change.

import { force, compression, viscous, viscousPower, swim } from "@penner/easing";

Includes force, compression, viscous, viscousDrag, viscousPower, swim, and swimAnalytic.

Tree-Shaking

Import only what you need to keep bundle sizes small:

// Import specific families
import { bounce, physicsSpring } from "@penner/easing";

// Import standard easings alongside physics-based ones
import { quadratic, cubic, back } from "@penner/easing";

Configuration Interfaces

BounceConfig

interface BounceConfig {
  bounces?: number; // Number of bounces (default: 4)
  decay?: number; // Total height decay as fraction 0-1 (default: 0.95)
}

SpringPhysicsConfig

interface SpringPhysicsConfig {
  bounces?: number; // Visible oscillation half-cycles (default: 4)
  decay?: number; // Total amplitude decay as fraction 0-1 (default: 0.95)
}

EasingKit

easingKit wraps any easing function into a callable bundle with CSS linear() approximations, velocity curve, and derivative functions — all lazily computed and cached on first access.

import { easingKit, physicsSpring } from "@penner/easing";

// Destructure the CSS strings you need
const { easing, velocity } = easingKit({ easingFn: physicsSpring.out() });

// Use easing for position, velocity for scale
element.style.animation = "move 2s both, scale 2s both";
element.style.animationTimingFunction = `${easing}, ${velocity}`;
@keyframes move {
  to {
    translate: 200px;
  }
}
@keyframes scale {
  from {
    scale: 0.8;
  }
}
// Or with the Web Animations API
element.animate([{ translate: "0px" }, { translate: "200px" }], {
  duration: 2000,
  easing,
  fill: "both",
});
element.animate([{ scale: 0.8 }, { scale: 1 }], { duration: 2000, easing: velocity, fill: "both" });

// Or keep the kit for callable use and derivatives
const kit = easingKit({ easingFn: spring.out() });
kit(0.5); // call as a plain easing function
kit.velocityFn(0.5); // numerical 1st derivative
kit.accelerationFn(0.5); // numerical 2nd derivative

You can supply exact analytical derivatives when the particular math formulas are known:

const kit = easingKit({
  easingFn: (t) => t * t,
  velocityFn: (t) => 2 * t, // exact derivative of t²
});

EasingKit types

interface EasingKitOptions {
  easingFn: EasingFn;
  velocityFn?: VelocityFn; // analytical 1st derivative
  accelerationFn?: AccelerationFn; // analytical 2nd derivative
  jerkFn?: JerkFn; // analytical 3rd derivative
  meta?: EasingKitMeta; // factory name + args for serialization
}

type EasingKit = EasingFn & {
  readonly easingFn: EasingKit; // self-reference for destructuring
  readonly easing: CSSEasing; // CSS linear() string
  readonly velocityFn: VelocityFn;
  readonly accelerationFn: AccelerationFn;
  readonly jerkFn: JerkFn;
  readonly velocity: CSSEasing; // velocity as CSS linear()
  readonly meta: EasingKitMeta;
  readonly toString: () => CSSEasing;
};

Utility Functions

Standalone utilities for working with easing functions. For bundled derivatives and CSS, see EasingKit above.

import {
  reverseEasingFn,
  mirrorEasingFnToRight,
  mirrorEasingFnToLeft,
  easingFnToCssLinear,
  createVelocityFn,
  createAccelerationFn,
  createJerkFn,
  withV0,
  withV0Family,
  softsignClamp,
  pruneColinearPoints,
  progressWave,
} from "@penner/easing";

// Reverse an easing function (swap start and end)
const myEaseIn = reverseEasingFn(quadratic.out);

// Mirror an easing to the right (ease-in becomes ease-in-out)
const myEaseInOut = mirrorEasingFnToRight(quadratic.in);

// Clamp values to 0-1 range
const safe = clamp01(someValue);

// Convert an easing function to a CSS linear() approximation
const css = easingFnToCssLinear(bounce.out());

// Create individual derivative functions
const velocity = createVelocityFn(bounce.out());
const acceleration = createAccelerationFn(bounce.out());
const jerk = createJerkFn(bounce.out());

// Add initial velocity to any ease-in
const withSlope = withV0(quadratic.in, 0.5); // f'(0) = 0.5
const anticipation = withV0(cubic.in, -0.3); // dips below 0

// Derive a full family with initial velocity
const family = withV0Family({ easeIn: quadratic.in, v0: 0.5 });

Advanced

The package also exports lower-level utilities for specialized use cases:

  • Spring conversion: pennerToSpring, springToPenner — convert between Penner easing parameters and spring physics parameters
  • Bezier fitting: fitCubicBezier — fit a cubic Bézier to an easing function
  • Heat map colors: velocityToRgb, velocityToHslString, hslToRgb — color utilities for visualizing easing derivatives
  • Back helpers: solveBackStrength, backEaseInVelocity
  • Spring helpers: springPhysicsFirstPeak, amplitudeRatio, dampingRate, settlingPhaseCorrection
  • Expo helpers: getExponentialEaseOutStartSlope, getExponentialEaseOutEndSlope, getExponentialEaseOutMetadata

Related Packages

  • @penner/classic-easing — the original Penner equations with classic naming conventions (easeOutQuad, easeInBounce, etc.)

Migration from Legacy Penner Functions

If you're migrating from classic Penner easing functions:

// Old: easeOutBack(t, b, c, d, s)
// New:
const backOut = back({ overshoot: s * 0.1 }).out; // Convert strength to overshoot fraction
const result = b + c * backOut(t / d);

// Old: easeOutBounce(t, b, c, d)
// New:
const bounceOut = bounce.out; // Uses sensible defaults
const result = b + c * bounceOut(t / d);

License

MIT - see LICENSE file for details.

About

Modern TypeScript implementations of the classic Penner easing functions with physics-based parameters.

Topics

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors