Skip to main content
Introducing packages.sweber.dev
Lagrangian overview

LagrangianLive demo. Click, throw, drop.

Everything on this page moves with real physics from @sweberdev/lagrangian: springs solved exactly, friction, gravity, collisions. Nothing has a fixed duration. Interrupt anything and it continues from where it is and how fast it moves.

Springs

Click faster than CSS can keep up.A transition restarts. A spring keeps its momentum.

Both dots go to the other side when you click. The upper one uses a CSS transition, the lower one a Lagrangian spring. Click several times in a row and watch the plot: the transition stops dead and starts again, the spring turns around smoothly because it knows its velocity.

The spring

import { animate } from "@sweberdev/lagrangian"

dot.addEventListener("click", () => {
  open = !open
  // Interrupting keeps the current velocity.
  animate(dot, { x: open ? 320 : 0 }, { duration: 0.6, bounce: 0.25 })
})

Throw

Throws that land where they should.Velocity, friction, snap points and soft edges.

Swipe the cards. Lagrangian measures how fast you let go, lets the strip glide with friction like iOS scrolling and adjusts the glide so it ends exactly on a card. Pull past the first or last card and it resists, then springs back. The puck on the right has no snap points, only walls.

Cards

01Mass
02Velocity
03Friction
04Gravity
05Restitution

Drag and let go

Free throw

Drag and let go

The cards

import { draggable, nearest } from "@sweberdev/lagrangian"

draggable(strip, {
  axis: "x",
  bounds: { left: -4 * step, right: 0 },
  snap: { x: [0, -step, -2 * step, -3 * step, -4 * step] },
})

draggable(puck, { bounds: box })

World

Drop things. Grab them. Throw them.Gravity, collisions, friction and rolling.

Pick up any ball and throw it. Bigger balls are heavier, collisions keep momentum, and friction at the contact point makes balls roll. Switch to the Moon or turn gravity off. When everything rests for a second the world stops computing, so an idle page costs nothing.

Gravity
Material

0 bodies · at rest, no CPU used

The playground

import { World } from "@sweberdev/lagrangian"

const world = new World({ bounds: box }).start()

for (const el of box.querySelectorAll(".ball")) {
  world.add({ x: 120, y: 0, radius: 24, restitution: 0.85, element: el })
}

world.bindPointer() // grab and throw
world.gravity = { x: 0, y: 1.62 } // the Moon

Double pendulum

Write your own equations of motion.Chaos from one Lagrangian.

Two double pendulums, started one thousandth of a radian apart. Their equations of motion come straight from the Lagrangian L = T − V and are stepped with RK4 at 240 Hz. For a few seconds they move as one, then they go completely different ways. The energy readout shows how little the integrator drifts.

Time 0.0 s · Energy drift 0.0000 %

The equations

import { loop, system } from "@sweberdev/lagrangian"

// State [θ1, θ2, ω1, ω2]; the accelerations follow
// from the Euler-Lagrange equations of L = T − V.
const pendulum = system(([a1, a2, w1, w2]) => {
  const d = a1 - a2, den = 3 - Math.cos(2 * d)
  return [
    w1,
    w2,
    (-3 * g * Math.sin(a1) - g * Math.sin(a1 - 2 * a2)
      - 2 * Math.sin(d) * (w2 * w2 + w1 * w1 * Math.cos(d))) / den,
    (2 * Math.sin(d) * (2 * w1 * w1 + 2 * g * Math.cos(a1)
      + w2 * w2 * Math.cos(d))) / den,
  ]
}, [angle, angle, 0, 0])

loop.add((dt) => draw(pendulum.advance(dt)))

Get it

About 7 kB of physics.Free and open source under the MIT licence.

Springs, throws, dragging and the world work with any framework. React hooks are in @sweberdev/lagrangian-react. The docs explain every option and the maths behind it.

pnpm add @sweberdev/lagrangian
Read the docs