@sweberdev/lagrangian 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +95 -0
- package/dist/index.cjs +1163 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +382 -0
- package/dist/index.d.ts +382 -0
- package/dist/index.js +1112 -0
- package/dist/index.js.map +1 -0
- package/package.json +73 -0
- package/src/animate.ts +172 -0
- package/src/decay.ts +127 -0
- package/src/drag.ts +174 -0
- package/src/index.ts +54 -0
- package/src/integrate.ts +61 -0
- package/src/loop.ts +73 -0
- package/src/reduced-motion.ts +17 -0
- package/src/spring.ts +140 -0
- package/src/value.ts +147 -0
- package/src/velocity.ts +50 -0
- package/src/world.ts +553 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Seya
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Lagrangian
|
|
2
|
+
|
|
3
|
+
Animation with real physics. Lagrangian moves interface elements the way objects move: **springs solved in closed form** that keep their velocity when you interrupt them, **throws with friction** that glide into bounds or land on snap points, **drag with iOS-style rubber-banding**, and a **small 2D world** with gravity, collisions, friction and rolling. Framework-agnostic, about 7 kB for everything.
|
|
4
|
+
|
|
5
|
+
**Docs and live demo:** [packages.sweber.dev/lagrangian](https://packages.sweber.dev/lagrangian)
|
|
6
|
+
|
|
7
|
+
| Package | What it does |
|
|
8
|
+
|---|---|
|
|
9
|
+
| [`@sweberdev/lagrangian`](packages/core) | `animate`, `value`, `draggable`, `spring`, `decay`, `World`, `rk4`, `system` |
|
|
10
|
+
| [`@sweberdev/lagrangian-react`](packages/react) | `useSpring`, `useSpringProps`, `useDraggable`, `useWorld`, `useBody`, `useValue` |
|
|
11
|
+
|
|
12
|
+
## Springs that can be interrupted
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { animate } from "@sweberdev/lagrangian";
|
|
16
|
+
|
|
17
|
+
animate(".card", { x: 240, rotate: 4 }, { duration: 0.6, bounce: 0.3 });
|
|
18
|
+
// Click again mid-flight: the card turns around with its current momentum, no jolt.
|
|
19
|
+
animate(".card", { x: 0, rotate: 0 }, { duration: 0.6, bounce: 0.3 });
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The spring is the exact solution of `m·x'' + c·x' + k·x = 0`, not a step-by-step approximation. It gives the same result at 30 and 144 Hz and knows its position and velocity at every moment.
|
|
23
|
+
|
|
24
|
+
## Throw, glide, snap
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { draggable } from "@sweberdev/lagrangian";
|
|
28
|
+
|
|
29
|
+
draggable(sheet, {
|
|
30
|
+
axis: "y",
|
|
31
|
+
bounds: { top: 0, bottom: 480 },
|
|
32
|
+
snap: { y: [0, 240, 480] },
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Velocity comes from a least-squares fit over the last 100 ms of pointer movement. On release the element glides with friction (`x(t) = x₀ + v₀·τ·(1 − e^(−t/τ))`) and lands exactly on the snap point nearest to where it would have stopped. Past the bounds it resists like iOS and springs back.
|
|
37
|
+
|
|
38
|
+
## A world with gravity
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { World } from "@sweberdev/lagrangian";
|
|
42
|
+
|
|
43
|
+
const world = new World({ bounds: container }).start();
|
|
44
|
+
world.add({ x: 120, y: 40, radius: 32, element: ball, restitution: 0.7 });
|
|
45
|
+
world.bindPointer(); // grab and throw
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Round bodies with mass, restitution and Coulomb friction, so they bounce, slide and roll. Springs between bodies, a pointer joint for grabbing, a fixed 240 Hz step and a world that stops using CPU when everything rests. Gravity is 9.81 m/s² at 500 px per meter unless you change it.
|
|
49
|
+
|
|
50
|
+
## Your own equations of motion
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { system } from "@sweberdev/lagrangian";
|
|
54
|
+
|
|
55
|
+
// A pendulum from its Lagrangian: θ'' = −(g/L)·sin θ
|
|
56
|
+
const pendulum = system(([theta, omega]) => [omega, -(9.81 / 1) * Math.sin(theta)], [1, 0]);
|
|
57
|
+
pendulum.advance(1 / 60);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## React
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
import { useDraggable, useSpringProps } from "@sweberdev/lagrangian-react";
|
|
64
|
+
|
|
65
|
+
const panel = useSpringProps<HTMLDivElement>({ x: open ? 0 : -320 }, { bounce: 0.2 });
|
|
66
|
+
const [handle] = useDraggable<HTMLDivElement>({ snap: { x: [0, 160] } });
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Reduced motion
|
|
70
|
+
|
|
71
|
+
When the user asks for reduced motion, springs and throws jump to where they would come to rest. Dragging and the world still follow the pointer, because the user is moving them. `setReducedMotion("never" | "always")` overrides this.
|
|
72
|
+
|
|
73
|
+
## Size
|
|
74
|
+
|
|
75
|
+
| Import | min+gzip |
|
|
76
|
+
|---|---|
|
|
77
|
+
| `spring` + `value` | about 3.4 kB |
|
|
78
|
+
| `animate` + `draggable` | about 5 kB |
|
|
79
|
+
| `World` | about 3.3 kB |
|
|
80
|
+
| everything | about 7 kB |
|
|
81
|
+
|
|
82
|
+
`pnpm size` checks these budgets in CI.
|
|
83
|
+
|
|
84
|
+
## Development
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
pnpm install
|
|
88
|
+
pnpm build && pnpm test && pnpm lint && pnpm size
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Changes go through pull requests with a changeset (`pnpm changeset`). Merging the version PR publishes to npm.
|
|
92
|
+
|
|
93
|
+
## Licence
|
|
94
|
+
|
|
95
|
+
MIT © Seya Weber
|