@penner/easing
v0.3.0
Published
Modern TypeScript implementations of the classic Penner easing functions with physics-based parameters
Maintainers
Readme
@penner/easing
Modern TypeScript implementations of the classic Penner easing functions with physics-based parameters and tree-shakeable exports.
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/easingQuick 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.5Easing Families
Power
Custom polynomial easings with any exponent, including fractional powers.
import { power } from "@penner/easing";
// Create custom power families
const sqrt = power({ exponent: 0.5 }); // Square root easing
const custom = power({ exponent: 2.5 }); // t^2.5 easing
const sqrtOut = sqrt.out;
const customIn = custom.in;
// All variants
const fam = power({ exponent: 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 (exponent: 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({ exponent: 4, overshoot: 0.2 });
const snappyOut = snappy.out;
const snappyInOut = snappy.inOut;interface FlexConfig {
exponent: 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); exponent: 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({ exponent: 2 });
// Softer than circular
const soft = superellipse({ exponent: 1.5 });
// Sharper, more squared-off
const sharp = superellipse({ exponent: 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) */
exponent: 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⁴ smoothExponential
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 derivativeYou 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.)@penner/responsive-easing— dynamically fuses head/tail easing curves with C¹ continuity, built on top of this package
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.
