Skip to main content
Introducing packages.sweber.dev
All packages
BetaMIT

LagrangianAnimation with real physics: springs that keep their momentum when interrupted, throws with friction onto snap points, and a small 2D world with gravity, collisions and rolling. About 7 kB.

pnpm add @sweberdev/lagrangian

Most web animation is a curve over a fixed duration. Click twice and the element stops dead and starts again; throw it and it ignores how fast you threw. Lagrangian animates with physics instead. Its springs are the exact solution of the damped oscillator, so they know their position and velocity at every moment and simply continue when the target changes, at any frame rate.

Drag an element and Lagrangian measures how fast you let go, lets it glide with friction like iOS scrolling, rubber-bands past the edges and lands exactly on the nearest snap point. For playful interfaces there is a small 2D world: round bodies with gravity, restitution and Coulomb friction, so they bounce, slide and roll, plus springs between bodies and a pointer joint for grabbing and throwing. When the user asks for reduced motion, automatic motion jumps to its end.

Example

A short exampleCopy it into your project.

app/drawer.tsx

import { draggable, World } from "@sweberdev/lagrangian"
import { useSpringProps } from "@sweberdev/lagrangian-react"

// Springs that keep their momentum when the state flips mid-flight
const panel = useSpringProps<HTMLElement>({ x: open ? 0 : -320 }, { bounce: 0.2 })

// Throw a sheet onto snap points
draggable(sheet, { axis: "y", snap: { y: [0, 240, 480] } })

// Drop things that bounce and roll
const world = new World({ bounds: box }).start()
world.add({ x: 120, y: 0, radius: 24, restitution: 0.8, element: ball })

Features

What you getFree under MIT, with Pro on top.

Free, MIT

  • Springs solved in closed form: exact at any frame rate, with duration and bounce or stiffness, damping and mass
  • Interrupt anything: every property keeps its velocity, so a new target continues the motion
  • animate() for transforms, opacity and CSS custom properties, with delay and stagger
  • draggable(): velocity from a least-squares fit, friction like iOS scrolling, rubber-banding past the bounds
  • Snap points the throw lands on exactly, by adjusting the glide instead of jumping
  • A 2D world: gravity, air drag, collisions with restitution, Coulomb friction and rolling
  • Springs between bodies, a pointer joint to grab and throw, sleep when everything rests
  • rk4() and system() for your own equations of motion
  • Reduced motion respected: automatic motion jumps to where it would rest
  • React hooks: useSpringProps, useSpring, useDraggable, useWorld, useBody
  • About 7 kB min+gzip for everything, checked in CI

FAQ

QuestionsShort answers.

How is this different from Sigmoid?

Sigmoid ties motion to the scroll position with native CSS scroll timelines and turns springs into CSS easing curves. Lagrangian runs physics in real time: interruptible springs, gestures, throws, gravity and collisions. They work well together.

Why not Motion or GSAP?

Both are excellent and do far more, such as layout animations and timelines. Lagrangian focuses on the physics: exact springs, throws that land on snap points and a world with collisions and rolling, in about 7 kB.

Is it a game physics engine?

No. Bodies are circles, which keeps the engine small and exact. It is meant for playful interfaces. For polygons, joints and thousands of bodies use Matter.js or Rapier.

What happens with reduced motion?

Springs and throws jump to where they would come to rest, so the layout ends up the same. Dragging and the world still follow the pointer, because the user is moving them.

Articles

ArticlesGuides and release notes.