npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@penner/responsive-easing

v0.1.0

Published

Responsive Easing is a library that dynamically generates easing functions for motion design that intelligently responds to varying conditions.

Downloads

27

Readme

Responsive Easing

Mix and merge easing curves to create motion that feels exactly right.

npm version

Install

npm install @penner/responsive-easing

Overview

Responsive Easing opens up new creative possibilities by splitting easing curves into two independent phases:

  • Head — the first phase of the curve, easing away from the start
  • Tail — the second phase, easing into the destination

The fuse() function seamlessly joins them at a configurable point, producing a single smooth easing function. Because each phase is independent, you can mix any head with any tail — anticipation into a bounce, a Bézier swoop into a spring, or anything in between — creating curves that would be impossible with a single easing function.

Use the Fuse Editor to visually design curves, tweak parameters in real time, and copy out CSS easing strings or config values for the JS API.

Quick start

import { fuse } from '@penner/responsive-easing';

// Backwards anticipation into a bouncy landing
const { easing, easingFn } = fuse({
  head: { kind: 'back', overshoot: 0.2 }, // backwards by 20%
  tail: { kind: 'spring', bounces: 4, decay: 0.95 },
  joinTime: 0.7, // head is 70% of the curve, tail is 30%
});

// `easing` is a CSS linear() string - for CSS transitions, animations and web animations
element.animate(keyframes, { duration: 500, easing });

// `easingFn` is a mathematical function — for JS-driven animation or direct evaluation
const y = easingFn(0.5);

The .easing property is a CSS linear() string, so it also works directly in style assignments:

element.style.transition = `transform 600ms ${easing}`;
element.style.animationTimingFunction = easing;

Easing definitions

Eight easing types, each with its own tunable parameters. Any head can be paired with any tail, giving you a wide palette of motion curves to design with.

| Kind | Parameters | Character | | ------------ | --------------------------- | -------------------------------------------------------- | | power | exponent | Smooth acceleration (quadratic, cubic, etc.) | | back | overshoot | Anticipation — dips backward before moving forward | | power-back | exponent, overshoot | Combined power + back | | bezier | x1, y1, x2, y2 | Cubic Bézier (same param space as CSS) | | expo | decay | Exponential decay (tail only) | | spring | bounces, decay | Damped oscillation (tail only) | | bounce | bounces, decay | Hard-surface rebound (tail only) | | swim | strokes, effort, drag | Rhythmic propulsion — pulsed force through viscous fluid |

The fuse() API

fuse() takes a plain config object describing head and tail easings and returns an EasingKit — a bundle containing the composed easing function, its CSS linear() string, and a velocity function.

function fuse(config: FuseConfig): EasingKit;

interface EasingKit {
  easing: CSSEasing; // CSS linear() string for transitions, animations, WAAPI
  easingFn: EasingFn; // (t: number) => number, [0,1] → [0,1]
  velocityFn: VelocityFn; // speed of the easing at any point in time
  meta: Record<string, unknown>;
}

FuseConfig

| Property | Type | Default | Description | | ---------- | ------------------------- | -------------- | ----------------------------------------------------- | | head | AnyEasingDef | — | Head easing definition (required) | | tail | AnyEasingDef | — | Tail easing definition (required) | | joinTime | number | 0.5 | Where head meets tail, in [0, 1] | | movement | 'transition' \| 'pulse' | 'transition' | Transition goes A→B; pulse goes A→B→A | | mirror | boolean | false | Tail = reversed head (symmetric inOut curve) | | maxSpeed | number | — | Cap join velocity; inserts a cruise phase if exceeded |

Examples

// Smooth power curve (equivalent to cubic ease-in-out when mirrored)
fuse({
  head: { kind: 'power', exponent: 3 },
  tail: { kind: 'power', exponent: 3 },
}).easingFn;

// Bézier head + bounce tail
fuse({
  head: { kind: 'bezier', x1: 0.4, y1: 1.6, x2: 0.8, y2: -0.4 },
  tail: { kind: 'bounce', bounces: 3, decay: 0.95 },
  joinTime: 0.3,
}).easingFn;

// Swim head + bounce tail
fuse({
  head: { kind: 'swim', strokes: 3.3, effort: 0.33, drag: 13 },
  tail: { kind: 'bounce', bounces: 1, decay: 0 },
}).easingFn;

// Symmetric curve via mirror
fuse({
  head: { kind: 'power-back', exponent: 2, overshoot: 0.1 },
  tail: { kind: 'power', exponent: 2 }, // ignored when mirror=true
  mirror: true,
}).easingFn;

Responsive Easing Modules (REMs)

Under the hood, each easing definition resolves to an EasingRem (Responsive Easing Module) — an object that bundles the easing math with metadata for building UIs like sliders and parameter editors.

import { PowerBackRem, SwimRem, getEasingRem } from '@penner/responsive-easing';

// Factory creation
const rem = getEasingRem('power-back', { exponent: 3, overshoot: 0.15 });

// Immutable update
const updated = rem.with({ overshoot: 0.3 });

// Evaluate
const y = rem.easeIn(0.5);

// Static knob specs for UI generation
PowerBackRem.EXPONENT_KNOB; // { key: 'exponent', type: 'number', min: 0.1, max: 10, ... }
SwimRem.DRAG_KNOB; // { key: 'drag', type: 'number', min: 0.5, max: 30, ... }

Available REMs: PowerRem, BackRem, PowerBackRem, BezierRem, ExpoRem, SpringRem, BounceRem, SwimRem.

Technical notes

  • The fuse config is a plain serializable object — no class instances or functions, just data. Easy to store in a database, share via URL, or pass between workers.
  • Types like NormalizedTime, CSSEasing, and EasingFn are powered by @penner/smart-primitive, which catches unit mix-ups at compile time with zero runtime cost.
  • Each easing definition resolves to an immutable EasingRem (Responsive Easing Module) that encapsulates the math, velocity computation, and UI knob metadata for a single easing type. REMs can be created, updated, and evaluated directly for advanced use cases.

Mathematical notes

The join between head and tail maintains C¹ continuity — both position and velocity (first derivative) match at the join point. This is what makes the transition feel smooth rather than abrupt.

Internally, fuse() scales each phase (head or tail) to fit its portion of the [0, 1] time range. The head is stretched or compressed to fill the interval [0, joinTime], and the tail fills [joinTime, 1]. Velocity at the join point is computed from the head's ending slope, and the tail's amplitude and time scale are adjusted so its starting slope matches — guaranteeing a seamless handoff regardless of which easing types are combined.

For easing types like spring, bounce, and swim where analytical derivatives aren't available, velocity is computed numerically.

License

MIT © Robert Penner