coily 0.15.0 → 0.16.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/README.md +131 -55
- package/dist/{config-OOMMRdK8.d.ts → config-B8M2xp26.d.ts} +103 -120
- package/dist/css.d.ts +85 -57
- package/dist/css.js +58 -34
- package/dist/index.d.ts +3 -3
- package/dist/index.js +98 -65
- package/dist/nuxt.d.ts +20 -19
- package/dist/nuxt.js +15 -5
- package/dist/{solver-CVXOTDkZ.js → solver-CAXuxdKj.js} +22 -14
- package/dist/system-C7PbI4L0.d.ts +1213 -0
- package/dist/{system-Br_cPnaN.js → system-WejqGkfv.js} +1948 -1216
- package/dist/vue.d.ts +224 -129
- package/dist/vue.js +161 -41
- package/package.json +1 -1
- package/dist/system-Du_fXXmO.d.ts +0 -940
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Simulate values using spring physics.
|
|
4
4
|
|
|
5
|
-
Coily animates numbers and
|
|
5
|
+
Coily animates numbers, and objects or arrays of numbers, with spring motion. It computes each frame from an exact formula for the spring's motion, for bouncy and non-bouncy springs alike, instead of stepping a simulation forward. Error does not build up over a long animation, and you can retarget a spring mid-flight without losing its momentum.
|
|
6
6
|
|
|
7
|
-
The core has zero dependencies and runs anywhere
|
|
7
|
+
The core has zero dependencies and runs anywhere. Outside the browser, it uses `setTimeout` in place of `requestAnimationFrame`. The Vue and Nuxt integrations are separate entry points, with `vue` and `@nuxt/kit` as optional peer dependencies. A third entry point, `coily/css`, turns a spring config into static CSS.
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
@@ -26,50 +26,51 @@ spring.onUpdate(() => {
|
|
|
26
26
|
ball.style.setProperty('--x', String(spring.value))
|
|
27
27
|
})
|
|
28
28
|
|
|
29
|
-
// Animate toward a new target at any time.
|
|
30
|
-
// carry over, so
|
|
29
|
+
// Animate toward a new target at any time. The value and velocity
|
|
30
|
+
// carry over, so an interruption stays smooth.
|
|
31
31
|
spring.target = 300
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
## Configuring springs
|
|
35
35
|
|
|
36
|
-
`defineSpring` accepts several
|
|
36
|
+
`defineSpring` accepts several combinations of options and derives the ones you leave out:
|
|
37
37
|
|
|
38
38
|
```ts
|
|
39
|
-
defineSpring({ tension: 500, damping: 40 }) //
|
|
39
|
+
defineSpring({ tension: 500, damping: 40 }) // stiffness and friction, set directly
|
|
40
40
|
defineSpring({ tension: 500, dampingRatio: 0.7 }) // damping derived
|
|
41
41
|
defineSpring({ tension: 500, bounce: 0.3 }) // bounce = 1 - dampingRatio
|
|
42
|
-
defineSpring({ duration: 750, dampingRatio: 1 }) // tuned to
|
|
42
|
+
defineSpring({ duration: 750, dampingRatio: 1 }) // tuned to rest at about 750 ms
|
|
43
43
|
defineSpring({ duration: 750, bounce: 0.5 })
|
|
44
|
-
defineSpring({ tension: 500, bounce: 0.9, arrival: 'stop' }) // dead
|
|
44
|
+
defineSpring({ tension: 500, bounce: 0.9, arrival: 'stop' }) // stops dead at the target, no overshoot
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
-
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
47
|
+
- `tension`: stiffness, greater than 0. Higher tension makes the whole motion faster.
|
|
48
|
+
- `damping`: friction, 0 or greater. Higher damping slows the spring down faster and reduces bounce.
|
|
49
|
+
- `dampingRatio`: friction as a fraction of the amount that just stops the bounce. At 0 the spring never settles. Below 1 it overshoots and bounces. At 1 (critically damped) it settles as fast as it can without overshoot. Above 1 (overdamped) it settles more slowly, still without overshoot.
|
|
50
|
+
- `bounce`: another way to set the damping ratio, as `1 - dampingRatio`. It goes from -1 (overdamped) to 1 (maximum bounce). A config that sets `damping` without `duration` needs it below 1.
|
|
51
|
+
- `duration`: the settle time in ms. The spring rests at this time, within one frame, at any `precision`. A bouncy config can rest up to about one oscillation earlier, or several short ones when `bounce` is near 1. The timing assumes that the motion starts 1 unit from the target. For accurate timing, set `displacement` to the range of your animation.
|
|
52
|
+
- `mass`: the weight of the moving value. Defaults to 1.
|
|
53
|
+
- `precision`: the number of decimal places that decide rest. Defaults to 2. A spring is _resting_ once its remaining motion cannot reach half a unit in the last place, which is 0.005 at the default. Coily never rounds values. A `duration` config is tuned against this threshold, so it still rests at its `duration`. Set `precision` to match the resolution of your values (see [PRECISION.md](https://github.com/tkofh/coily/blob/main/PRECISION.md)).
|
|
54
|
+
- `arrival`: what the motion does when the value reaches the target. `'passthrough'` (the default) swings through and settles. `'stop'` ends the motion exactly on the target the first time the value gets there, however bouncy the config. A number between -1 and 1 multiplies the velocity at every crossing, and negative values rebound. A spring that never crosses its target ignores `arrival`. For example, a spring with a `dampingRatio` of 1 or above that starts from rest closes in from one side and never crosses. Coily solves each crossing exactly and never samples it frame by frame (see [PRECISION.md](https://github.com/tkofh/coily/blob/main/PRECISION.md)).
|
|
55
55
|
|
|
56
|
-
Without a config,
|
|
56
|
+
Without a config, a spring is critically damped and settles in about 500 ms.
|
|
57
57
|
|
|
58
58
|
## Spring API
|
|
59
59
|
|
|
60
|
-
- `spring.target`: set to animate toward a new value
|
|
61
|
-
- `spring.value`: current value
|
|
62
|
-
- `spring.velocity`: current velocity,
|
|
63
|
-
- `spring.
|
|
64
|
-
- `spring.
|
|
65
|
-
- `spring.
|
|
66
|
-
- `spring.
|
|
67
|
-
- `spring.
|
|
68
|
-
- `spring.
|
|
60
|
+
- `spring.target`: set it to animate toward a new value.
|
|
61
|
+
- `spring.value`: the current value. Write it to displace the spring.
|
|
62
|
+
- `spring.velocity`: the current velocity, in units per second. Write it to fling the spring.
|
|
63
|
+
- `spring.acceleration`: the current acceleration, in units per second squared. Read-only.
|
|
64
|
+
- `spring.jumpTo(v)`: snap to a value with no animation.
|
|
65
|
+
- `spring.config`: assign a new `SpringDefinition`, or `null` to revert to the default.
|
|
66
|
+
- `spring.isResting`, `spring.timeRemaining`: whether the spring is resting, and the ms until it rests. Coily solves the time from the motion and does not estimate it (see [PRECISION.md](https://github.com/tkofh/coily/blob/main/PRECISION.md)).
|
|
67
|
+
- `spring.settled`: a promise that resolves when the spring next comes to rest, or at once if it is already resting. A retarget mid-flight extends the wait, and `dispose()` resolves it. Use `await spring.settled` to run animations in sequence.
|
|
68
|
+
- `spring.onUpdate(cb)`, `onStart(cb)`, `onStop(cb)`, `onDispose(cb)`: subscribe to events. Each returns an unsubscribe function. `start` fires when the spring leaves rest, and `stop` fires when it settles. The two always alternate, and a retarget mid-flight fires neither.
|
|
69
|
+
- `spring.dispose()`: release the spring. A second call does nothing.
|
|
69
70
|
|
|
70
71
|
### Chaining
|
|
71
72
|
|
|
72
|
-
A spring can follow another spring's live value instead of a fixed number
|
|
73
|
+
A spring can follow another spring's live value instead of a fixed number. Assign a spring to `target`, or pass one to `createSpring` to follow it from creation. `mapSpring` derives new values that springs can follow, such as offsets, mirrors, clamps, or any pure function of the value:
|
|
73
74
|
|
|
74
75
|
```ts
|
|
75
76
|
const leader = system.createSpring(0)
|
|
@@ -79,9 +80,9 @@ const trailing = system.createSpring(mapSpring(leader, (v) => v + 20))
|
|
|
79
80
|
const mirrored = system.createSpring(mapSpring(leader, (v) => -v))
|
|
80
81
|
```
|
|
81
82
|
|
|
82
|
-
A follower
|
|
83
|
+
A follower keeps its own config. Following changes what a spring chases, never how it moves. To match a leader's feel, pass the leader's config at creation, as in `system.createSpring(leader, leader.config)`, or share one `defineSpring` constant. Assign a number to `target` to stop following.
|
|
83
84
|
|
|
84
|
-
`mapSpring` also combines several springs
|
|
85
|
+
`mapSpring` also combines several springs. Pass a shape, which is a plain object or array with springs at the leaves, nested to any depth, and a function of their values:
|
|
85
86
|
|
|
86
87
|
```ts
|
|
87
88
|
const x = system.createSpring(0)
|
|
@@ -90,7 +91,7 @@ const y = system.createSpring(0)
|
|
|
90
91
|
const distance = system.createSpring(mapSpring({ x, y }, ({ x, y }) => Math.hypot(x, y)))
|
|
91
92
|
```
|
|
92
93
|
|
|
93
|
-
|
|
94
|
+
A composite spring (see below) is a source of its whole value shape, so one map can derive a single number from every channel at once:
|
|
94
95
|
|
|
95
96
|
```ts
|
|
96
97
|
const point = system.createSpring({ x: 3, y: 4 })
|
|
@@ -98,38 +99,40 @@ const point = system.createSpring({ x: 3, y: 4 })
|
|
|
98
99
|
const magnitude = system.createSpring(mapSpring(point, ({ x, y }) => Math.hypot(x, y)))
|
|
99
100
|
```
|
|
100
101
|
|
|
101
|
-
A mapped value is a `SpringSource
|
|
102
|
+
A mapped value is a `SpringSource`. `target` accepts this type, and every `Spring` implements it. A `CompositeSpring` is a source _of its shape_. `mapSpring` can read it, alone or at the leaves of a shape, but a single-number spring can follow only a single-number source.
|
|
102
103
|
|
|
103
|
-
Chains advance in dependency order
|
|
104
|
+
Chains advance in dependency order. Each frame, a spring moves after the springs it follows, whatever order you created or connected them in. A follower therefore chases its leader's value for the current frame, not the previous one. Update events fire once per spring per frame, after every spring has moved, in the same leader-first order. A callback that reads another spring sees that spring's final value for the frame. A spring that a callback retargets starts moving on the next frame.
|
|
104
105
|
|
|
105
|
-
Following is accurate within
|
|
106
|
+
Following is accurate within a frame, too. A follower moves against the path its leader traveled during the frame, not one value sampled at the frame boundary. Sometimes one frame carries more motion than the system's `couplingTolerance` allows, which is 0.1 value units by default. The system then splits that frame into internal sub-steps. Dropped frames and targets that jump get these sub-steps automatically, and update events still fire once per frame. As a result, a chain keeps the same shape at 30fps as at 120fps. See [PRECISION.md](https://github.com/tkofh/coily/blob/main/PRECISION.md) for the model.
|
|
106
107
|
|
|
107
|
-
Follow graphs
|
|
108
|
+
Follow graphs can contain cycles. A retarget emits no `update`, so nothing loops within a frame. Mutual followers converge to a shared value, and that value is the same at any frame rate. A cycle has no order that puts every member after its leaders. Its members advance in creation order, and the edge that closes the loop chases one frame behind. When the maps in a cycle push the values apart, such as `(v) => v + 10` in both directions, the springs chase forever and never rest. The system stays awake until you break the cycle.
|
|
108
109
|
|
|
109
110
|
### Composites
|
|
110
111
|
|
|
111
|
-
`createSpring` also takes
|
|
112
|
+
`createSpring` also takes a numeric shape: a plain object or array, nested to any depth, whose leaves are all numbers. Each leaf becomes an independent channel behind one composite API, a `CompositeSpring`:
|
|
112
113
|
|
|
113
114
|
```ts
|
|
114
115
|
const spring = system.createSpring({ position: { x: 0, y: 0 }, opacity: 1 })
|
|
115
116
|
|
|
116
|
-
spring.target = { position: { x: 100 } } // a partial target leaves other channels alone
|
|
117
|
-
spring.value // { position: { x, y }, opacity }
|
|
117
|
+
spring.target = { position: { x: 100 } } // a partial target leaves the other channels alone
|
|
118
|
+
spring.value // { position: { x, y }, opacity }, a read-only object that every read refreshes
|
|
118
119
|
```
|
|
119
120
|
|
|
120
|
-
The shape is fixed at creation
|
|
121
|
+
The shape is fixed at creation. An unknown channel throws an error that names its path (`position.z`). `value`, `velocity`, and `jumpTo` take the same partial shapes. A composite coalesces its events. `update` fires at most once per frame, with every channel in its final state for that frame, and `stop` always comes after that frame's final `update`. `settled` and reduced motion apply channel by channel.
|
|
121
122
|
|
|
122
|
-
The `Shape` type
|
|
123
|
+
The `Shape` type, exported from `coily`, also checks shapes at compile time. Interfaces such as your own `Vector2` work without index signatures. It rejects channels that are non-numeric, optional, or typed `undefined` where you declare them.
|
|
123
124
|
|
|
124
|
-
Configs apply per channel. Pass
|
|
125
|
+
Configs apply per channel. Pass one `SpringDefinition` for every channel, or a shape that mirrors the value. The shape can hold a config, or `null` to revert to the default, at any level. A config at a subtree covers every channel below it:
|
|
125
126
|
|
|
126
127
|
```ts
|
|
128
|
+
const stiff = defineSpring({ tension: 800, dampingRatio: 1 })
|
|
129
|
+
|
|
127
130
|
spring.config = { position: stiff, opacity: defineSpring({ duration: 300, dampingRatio: 1 }) }
|
|
128
131
|
```
|
|
129
132
|
|
|
130
|
-
|
|
133
|
+
A config is a `SpringDefinition` instance, built with `defineSpring`. A config position does not accept a bare options object. Coily always reads a plain object as a per-channel shape, so any leaf in it that is not a config throws an error that names its path.
|
|
131
134
|
|
|
132
|
-
|
|
135
|
+
A composite spring follows channel by channel. To follow another composite spring as a whole, assign it to `target`. The two must have exactly the same shape. You can also name channels one at a time, and each channel of a partial target takes a number or a single-number `SpringSource`. While the composite follows, a partial target detaches only the channels it names:
|
|
133
136
|
|
|
134
137
|
```ts
|
|
135
138
|
follower.target = leader
|
|
@@ -141,28 +144,74 @@ follower.target = {
|
|
|
141
144
|
}
|
|
142
145
|
```
|
|
143
146
|
|
|
147
|
+
### Velocity, acceleration, and target
|
|
148
|
+
|
|
149
|
+
`velocityOf`, `accelerationOf`, and `targetOf` each turn one part of a moving value into a source. `velocityOf` reads its velocity in units per second, `accelerationOf` reads its acceleration in units per second squared, and `targetOf` reads where it is heading. A spring can follow the result, and `mapSpring` can shape it:
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
const x = system.createSpring(0)
|
|
153
|
+
|
|
154
|
+
// A blur that grows with speed: a spring follows a map of x's velocity.
|
|
155
|
+
const blur = system.createSpring(mapSpring(velocityOf(x), (v) => Math.abs(v) / 100))
|
|
156
|
+
|
|
157
|
+
// The distance x has left to travel, measured against where it is heading.
|
|
158
|
+
const remaining = system.createSpring(
|
|
159
|
+
mapSpring({ value: x, target: targetOf(x) }, ({ value, target }) => target - value),
|
|
160
|
+
)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
All three accept a `Spring`, a `CompositeSpring`, a ramp, or a Vue ref that wraps one. A composite gives a result of the same shape. A `mapSpring` result has no velocity or target of its own, so all three reject it. The result is a view, not a spring, and needs no disposal.
|
|
164
|
+
|
|
165
|
+
### Ramps
|
|
166
|
+
|
|
167
|
+
A ramp is a value that moves toward its target at a fixed speed, in a straight line, and stops there. It is a source like a spring, so a spring can follow it:
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
// An endless spin at 270 deg/s. The spring trails it like an object with inertia.
|
|
171
|
+
const sweep = system.createRamp(0, { rate: 270, target: Infinity })
|
|
172
|
+
const ring = system.createSpring(sweep)
|
|
173
|
+
|
|
174
|
+
// A heading that turns at no more than 400 deg/s: retarget the ramp, not the spring.
|
|
175
|
+
const slew = system.createRamp(0, { rate: 400 })
|
|
176
|
+
const core = system.createSpring(slew)
|
|
177
|
+
slew.target = 180
|
|
178
|
+
|
|
179
|
+
// A clock in ms, mapped into a 600 ms pulse.
|
|
180
|
+
const clock = system.createRamp(0, { rate: 1000, target: 600 })
|
|
181
|
+
const pulse = system.createSpring(mapSpring(clock, (ms) => Math.sin((Math.PI * ms) / 600)))
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
With the default `arrival`, following a ramp is exact. The spring moves against the ramp's steady motion. With any other `arrival`, the spring holds the target still within each of a frame's internal sub-steps. By contrast, a target that you write once per frame reaches the spring as a staircase, about half a frame behind.
|
|
185
|
+
|
|
186
|
+
A ramp has the parts of the spring API that apply to it. These are `value`, `target`, `rate`, a read-only `velocity`, `isResting`, `timeRemaining` in ms, `settled`, `jumpTo`, `dispose`, and the same four events. You can write `value`, and the ramp carries on from where you put it. A `target` of `Infinity` or `-Infinity` makes the ramp endless. `rate` is in units per second. `velocityOf`, `accelerationOf`, and `targetOf` accept a ramp.
|
|
187
|
+
|
|
188
|
+
A ramp's value is a function of time. A ramp costs nothing per frame unless something listens to it frame by frame. An update listener does, and every follower is one. On a bounded ramp, a stop listener or a pending `settled` does too. A ramp that nothing watches keeps moving on the system's clock and reports its position when you read it. The clock counts frames, `advance` calls, and the time a started system sleeps. It stops while the system is stopped. A sleeping system also skips the time the page is hidden. An awake system gets no frames in a hidden tab, so the hidden spell counts as one frame when the page returns, clamped like any other lag.
|
|
189
|
+
|
|
144
190
|
## Reduced motion
|
|
145
191
|
|
|
146
|
-
Coily
|
|
192
|
+
Coily follows `prefers-reduced-motion` by default. When it is on, springs snap to their targets instead of animating. Retargets and value writes apply at once, and springs ignore velocity writes. Events stay consistent: one `update` per change, no `start` or `stop`, and `settled` resolves at once. Code written for the animated path keeps working.
|
|
193
|
+
|
|
194
|
+
Ramps follow the same rule. A ramp jumps to a bounded target, and an endless ramp holds still until reduced motion turns off. A value that moves nothing on screen, such as a cross-fade, a color, or a clock that drives logic, takes `purpose: 'nonmotion'` and keeps animating. `createSpring`, `createRamp`, `useSpring`, and `useRamp` take `purpose` in their options, and `<SpringValue>` takes it as a prop.
|
|
147
195
|
|
|
148
|
-
Control it with the `reducedMotion` system option: `'always'`, `'never'`, or the default `'user'`, which follows the OS setting
|
|
196
|
+
Control it with the `reducedMotion` system option: `'always'`, `'never'`, or the default `'user'`, which follows the OS setting. When the OS setting turns on, animations in flight finish at once. Read `system.reducedMotion` to turn off decorative effects in your own code, such as particles.
|
|
149
197
|
|
|
150
198
|
## Vue
|
|
151
199
|
|
|
152
|
-
Call `useSpringSystem()` once near the root of your app. It returns the spring system
|
|
200
|
+
Call `useSpringSystem()` once near the root of your app. It returns the spring system that this component or an ancestor provides. If none exists, it creates one, provides it to descendants, starts it on mount, and disposes it on unmount. A second call in any descendant returns the same system. Options apply only when the call creates a system:
|
|
153
201
|
|
|
154
202
|
```ts
|
|
155
203
|
import { useSpringSystem } from 'coily/vue'
|
|
156
204
|
|
|
157
|
-
const system = useSpringSystem() //
|
|
205
|
+
const system = useSpringSystem() // for example, read system.reducedMotion
|
|
158
206
|
```
|
|
159
207
|
|
|
160
|
-
|
|
208
|
+
`provideSpringSystem(system, app?)` supplies a system that you created yourself, which is mostly useful in tests. You call `start()`, `stop()`, and `dispose()` on that system yourself.
|
|
161
209
|
|
|
162
210
|
Then animate anywhere below:
|
|
163
211
|
|
|
164
212
|
```vue
|
|
165
213
|
<script setup>
|
|
214
|
+
import { ref } from 'vue'
|
|
166
215
|
import { useSpring } from 'coily/vue'
|
|
167
216
|
|
|
168
217
|
const target = ref(0)
|
|
@@ -174,15 +223,22 @@ const x = useSpring(target, { duration: 500, bounce: 0.3 })
|
|
|
174
223
|
</template>
|
|
175
224
|
```
|
|
176
225
|
|
|
177
|
-
`useSpring` returns a `SpringRef`: a writable ref of the animated value with `velocity`, `isResting`, and `timeRemaining` refs
|
|
226
|
+
`useSpring` returns a `SpringRef`: a writable ref of the animated value, with `velocity`, `isResting`, and `timeRemaining` refs, `settled`, and `jumpTo()` attached. The target can be a ref, a getter, or another `SpringRef`, which chains the two springs. The config is reactive too. When it changes, the spring reconfigures in place.
|
|
178
227
|
|
|
179
|
-
A `SpringRef` is also a `SpringSource`, and `useSpring` follows any source
|
|
228
|
+
A `SpringRef` is also a `SpringSource`, and `useSpring` follows any source. Chain through `mapSpring`, as in `useSpring(mapSpring({ x, y }, ({ x, y }) => Math.hypot(x, y)))`. Or pass a getter that returns a source to switch leaders reactively, as in `useSpring(() => (split.value ? left : right))`. Following bypasses Vue reactivity, because a follower subscribes to the backing spring directly. An effect or getter therefore never gains a dependency on a leader's animation.
|
|
180
229
|
|
|
181
|
-
Numeric shapes work the same way
|
|
230
|
+
Numeric shapes work the same way. Pass a record or array, as a plain value, a ref, or a getter, and you get a `CompositeSpringRef`. Reads return the deep-readonly composite value, and writes take partial shapes. The config can be a reactive per-channel config shape. Pass another `CompositeSpringRef` to follow it channel by channel. A deeply reactive target retargets when a nested property changes. For several _independent_ single-number springs, map over the targets, since composables are safe to call in a loop: `targets.map((t) => useSpring(t))`.
|
|
231
|
+
|
|
232
|
+
`useRamp(target, rate, { from, purpose })` returns a `RampRef`: a writable ref of a ramp's value, with `velocity`, `isResting`, and `timeRemaining` refs, `settled`, and `jumpTo()`. `target` and `rate` take numbers, refs, or getters. `from` defaults to the initial target, and you must pass it when the target starts infinite. The ramp stays in the frame loop while something reads the ref, and `useSpring(rampRef)` follows the ramp directly:
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
const angle = useRamp(Infinity, 270, { from: 0 })
|
|
236
|
+
const ring = useSpring(angle, { duration: 300, dampingRatio: 1 })
|
|
237
|
+
```
|
|
182
238
|
|
|
183
|
-
|
|
239
|
+
A renderless `<SpringValue :target="n">` component exposes `{ value, velocity, isResting, timeRemaining, jumpTo }` through its default slot.
|
|
184
240
|
|
|
185
|
-
`useSpringPool()`
|
|
241
|
+
`useSpringPool()` is for imperative work: a changing set of springs that you create and dispose at any time, such as particles or per-item effects. It returns `createSpring` bound to the provided system. When the component's scope ends, the pool disposes every spring created through it, so no pooled spring outlives its component. You can still dispose a spring yourself before that.
|
|
186
242
|
|
|
187
243
|
## Nuxt
|
|
188
244
|
|
|
@@ -190,17 +246,37 @@ There's also a renderless `<SpringValue :target="n">` component exposing `{ valu
|
|
|
190
246
|
export default defineNuxtConfig({
|
|
191
247
|
modules: ['coily/nuxt'],
|
|
192
248
|
coily: {
|
|
193
|
-
debug: false, //
|
|
249
|
+
debug: false, // log active motion counts to the console
|
|
194
250
|
reducedMotion: 'user', // 'user' | 'always' | 'never'
|
|
195
251
|
},
|
|
196
252
|
})
|
|
197
253
|
```
|
|
198
254
|
|
|
199
|
-
The module provides
|
|
255
|
+
The module provides one spring system for the whole app and starts it on the client. It auto-imports `useSpring`, `useRamp`, `useSpringSystem`, `useSpringPool`, `defineSpring`, `mapSpring`, `velocityOf`, `accelerationOf`, and `targetOf`, and registers the `SpringValue` component. The `coily` key also accepts the system options `fps`, `lagThreshold`, `adjustedLag`, and `couplingTolerance`.
|
|
256
|
+
|
|
257
|
+
## CSS
|
|
258
|
+
|
|
259
|
+
`coily/css` turns a spring config into static CSS: a `linear()` easing and a duration in ms to pair it with. It needs no DOM and no ticker, so it can run at build time:
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
import { defineSpring } from 'coily'
|
|
263
|
+
import { springToWaapi } from 'coily/css'
|
|
264
|
+
|
|
265
|
+
const config = defineSpring({ duration: 500, bounce: 0.3, displacement: 300 })
|
|
266
|
+
const { keyframes, options } = springToWaapi(config, {
|
|
267
|
+
property: 'translate',
|
|
268
|
+
from: 0,
|
|
269
|
+
to: 300,
|
|
270
|
+
unit: 'px',
|
|
271
|
+
})
|
|
272
|
+
element.animate(keyframes, options)
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
`springToLinear` returns the easing and its duration alone. `springToCss` returns a `@keyframes` rule and an `animation` value, and `springToTransition` returns a `transition` value. Give a spec a `velocity` to start the animation or transition at a moving value's speed, such as a drag at release. `springFromState` and `springStateAt` let you continue an interrupted animation with its momentum. A config with a `dampingRatio` of 0 and the default `arrival` never rests. `springToLinear`, `springToWaapi`, and `springToCss` turn it into an endless alternating loop, and `springToTransition` throws for it.
|
|
200
276
|
|
|
201
277
|
## Timing
|
|
202
278
|
|
|
203
|
-
The built-in ticker advances springs once per displayed frame, so motion is as smooth as the screen
|
|
279
|
+
The built-in ticker advances springs once per displayed frame, so motion is as smooth as the screen allows: 60Hz, 120Hz, or adaptive sync. The loop sleeps while every spring is resting, so an idle system schedules no frames. It wakes on the next write. Set `fps`, as an option or a property, to cap the rate. Capped ticks land on whole display frames and still receive the true elapsed time. The ticker clamps large gaps between frames, such as when a backgrounded tab comes back, so springs do not teleport. A gap longer than `lagThreshold` (default 500 ms) counts as `adjustedLag` (default 33 ms). Set `lagThreshold` to 0 to turn lag clamping off. For manual stepping, skip `system.start()` and call `system.advance(dtMs)` yourself, with a `dtMs` of 0 or more. Each system listens for page visibility and, by default, the reduced-motion setting. Call `system.dispose()` to remove those listeners when you no longer need the system.
|
|
204
280
|
|
|
205
281
|
## License
|
|
206
282
|
|