Skip to content
kaltwrkPublic

About

Analytical spring solver for GSAP

Resources

Contributing

Security policy

Stars

5 stars

Watchers

0 watching

Forks

Repository files navigation

Tensum

Tensum is a GSAP plugin backed by an analytical spring solver. GSAP owns the timeline, scheduling, and context lifecycle. When GSAP renders the plugin, Spring samples position and velocity from absolute time and writes the sampled property values.

The repository contains one public package:

tensum

The public spring configuration has no required duration. Duration comes from the physical state and settling tolerances.

Install

pnpm add tensum gsap

Both packages are required. GSAP is a peer dependency so an application and the plugin use the same GSAP instance.

Register the plugin

import { gsap } from "gsap";
import { TensumPlugin, springPresets } from "tensum";

gsap.registerPlugin(TensumPlugin);

Compose springs in a GSAP timeline

Use the registered timeline.spring() effect when a spring's derived duration affects timeline layout. The effect calculates the duration before GSAP inserts the tween, so sequential children, staggered targets, and nested timelines have their final timing immediately.

const timeline = gsap.timeline();

timeline
  .spring(element, {
    x: 320,
    from: { x: 0 },
    parameters: { mass: 1, stiffness: 180, damping: 24 },
  })
  .spring(element, {
    x: 80,
    from: { x: 320 },
    parameters: { mass: 1, stiffness: 240, damping: 26 },
  });

from declares the state from which preflight should calculate. Omit it when the target already has the right value while the effect is being created. Set it when an earlier timeline child will change that value before this spring starts. Use the existing velocity option when the future state also has a known velocity.

Pass ordinary GSAP options through tween. The effect owns duration and ease. Finite springs support options such as stagger, repeat, and yoyo. An unsettled spring using continue owns an infinite driver instead; it sets repeat: -1 and samples analytical total time continuously.

const entrance = gsap.timeline().spring(cards, {
  y: 0,
  from: { y: -24 },
  parameters: springPresets.snappy(),
  tween: { stagger: 0.06, repeat: 1, yoyo: true },
});

masterTimeline.add(entrance, 0.4);

createMotionSpringTween(targets, vars) exposes the same preflight step when code needs the tween itself rather than the extended timeline method.

Use the controller API

springTo() is the direct API for cases that need a controller rather than a timeline child.

import { springTo } from "tensum";

const animation = springTo(element, {
  x: 600,
  rotation: "30deg",
  velocity: { x: 1250 },
  spring: {
    mass: 1,
    stiffness: 180,
    damping: 24,
    settle: { position: 0.1, velocity: 0.1 },
  },
  properties: {
    rotation: { damping: 30, velocity: 90 },
  },
});

animation.pause();
animation.seek(0.2);
animation.resume();

Built-in transform properties are x, y, scale, and rotation. Use targets for other numeric GSAP properties or adapters for custom read/write behavior. Numeric strings may contain one unit, such as 24px or 30deg. A unit mismatch throws before animation starts. DOM x/y values default to pixels and DOM rotation defaults to degrees; ordinary object properties remain unitless numbers unless their value or configuration explicitly supplies a unit.

Starting another springTo() animation or constructing a timeline spring on the same target and property performs an automatic velocity-preserving handoff from the state that exists at construction time. Provide from and velocity for a state that will only exist later in the timeline.

After normal completion, the last analytical state remains available for a later Tensum handoff. This includes the non-zero velocity of an unsettled spring stopped at maxDuration. If application code writes a different value after completion, the next implicit handoff detects that change, discards the terminal history, and starts from the value currently on the target. An explicit from always supplies the effect's starting snapshot.

Physical and perceptual parameters

Supply physical parameters directly:

const parameters = {
  mass: 1,
  stiffness: 180,
  damping: 24,
};

Or derive them from product-facing controls:

import { springParameters, springPresets } from "tensum";

const tuned = springParameters.fromPerceptualDuration({
  duration: 0.5,
  bounce: 0.15,
});

const snappy = springPresets.snappy();

All time values are seconds. Velocity is property units per second. The solver is unit-agnostic; x may use pixels while rotation uses degrees.

Settlement

A damped spring approaches equilibrium asymptotically. The plugin treats a spring as physically settled when both conditions remain true for its future tail:

abs(position - target) <= positionEpsilon
abs(velocity) <= velocityEpsilon

Absolute tolerances allow a longer movement to take longer to settle even when its normalized response is the same.

An undamped moving spring never reaches physical rest. Choose an unsettled policy explicitly when this is possible:

  • stop freezes at maxDuration and is the default;
  • snap writes the target at maxDuration;
  • continue keeps an infinite GSAP clock running;
  • error rejects the animation.

For a finite driver, logical completion is clamped to its completion boundary. This includes the maxDuration boundary for stop and snap. continue retains the requested perceptual duration. onSettle does not run for a spring that failed to settle. Effect lifecycle callbacks are target-scoped: an array tween calls the same callback once per target, and each snapshot contains only that target's spring states.

GSAP lifecycle details

Finite repeats and yoyo playback use cycle-local spring time. A yoyo handoff reverses the inherited velocity as well as the position path. Killing one property removes only that spring track; killing the last track releases the internal driver. If the killed track made an unsettled continue tween infinite, the remaining duration and original repeat count are restored.

Calling invalidate() keeps the snapshot and duration captured when the effect was created; construct a new effect tween to preflight different inputs. Kill, interrupt, and context revert release active ownership. As with native GSAP tweens, context revert restores the baseline captured when the effect was constructed.

Supporting exports

The root entry point also exports createSpring, parameter converters, velocity helpers, spring values, keyframes, inertia, additive composition, and vector springs.

Advanced CSS and coupled-system utilities remain package subpaths:

import { springToCSSLinear } from "tensum/css";
import { createCoupledSpringSystem } from "tensum/coupled";

The CSS exporter treats maxError as a hard normalized-progress bound for the serialized linear() function. If duration, precision, maxDepth, or maxSamples make that bound impossible, it throws instead of returning an under-sampled curve.

Repository structure

packages/tensum   one public GSAP plugin package
apps/site         Svelte product site and interactive playground

Inside the package, the analytical solver stays separate from the GSAP adapter so sampling remains deterministic. This is an implementation boundary, not another public library.

Development

pnpm install
pnpm dev
pnpm test
pnpm check
pnpm lint
pnpm build
pnpm benchmark
pnpm release:check

The repository toolchain requires Node.js 22.12 or newer. The published package is additionally tested on Node.js 20.19.x; its complete runtime, browser, TypeScript, and GSAP matrix is defined in the platform support baseline.

pnpm release:check builds every workspace, runs all checks, validates the complete named value and type export surface, validates the package manifest, packs a tarball, and installs that tarball in a temporary consumer project. It does not publish or tag a release.

The package is ESM-only. TypeScript consumers should use node16, nodenext, or bundler module resolution. CommonJS code can load it with dynamic import(), but require() is intentionally unsupported. Version 0.x may contain breaking API changes.

See the package README for the full public API and the changelog for release status.

License

MIT, copyright 2026 Motion Core. See LICENSE.

About

Analytical spring solver for GSAP

Resources

Contributing

Security policy

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages