@thednp/tween 0.0.1
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 +259 -0
- package/dist/index.cjs +621 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +184 -0
- package/dist/index.d.mts +184 -0
- package/dist/index.mjs +610 -0
- package/dist/index.mjs.map +1 -0
- package/dist/react.cjs +61 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +9 -0
- package/dist/react.d.mts +9 -0
- package/dist/react.mjs +55 -0
- package/dist/react.mjs.map +1 -0
- package/dist/solid.cjs +81 -0
- package/dist/solid.cjs.map +1 -0
- package/dist/solid.d.cts +9 -0
- package/dist/solid.d.mts +9 -0
- package/dist/solid.mjs +75 -0
- package/dist/solid.mjs.map +1 -0
- package/dist/tween.iife.js +2 -0
- package/dist/tween.iife.js.map +1 -0
- package/package.json +96 -0
- package/wiki/Easing.md +58 -0
- package/wiki/React.md +255 -0
- package/wiki/Solid.md +149 -0
- package/wiki/Timeline.md +207 -0
- package/wiki/Tween.md +230 -0
package/wiki/React.md
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
## React Hooks for @thednp/tween
|
|
2
|
+
|
|
3
|
+
Simple, performant hooks that integrate `Tween` and `Timeline` seamlessly with React state.
|
|
4
|
+
|
|
5
|
+
They handle React's frequent re-renders and `<StrictMode>` double-mounting safely, so you can focus on animation logic instead of fighting the framework.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
Don't forget to install.
|
|
9
|
+
```
|
|
10
|
+
npm install @thednp/tween
|
|
11
|
+
# or pnpm/yarn/bun/deno
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
### useTween
|
|
15
|
+
Creates a new [`state`, `tween`, `configureTween`] tuple that updates from current values and updates React's state on every frame.
|
|
16
|
+
|
|
17
|
+
To eliminate the possibility of JANK or values overprocessing, it is recommended that you wrap the configuration of your persistent `Tween` objects inside wrappers, like shown in the examples below:
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
#### Example 1
|
|
21
|
+
|
|
22
|
+
Use all available items of the hook - **recommended**.
|
|
23
|
+
```tsx
|
|
24
|
+
import { useTween } from "@thednp/tween/react";
|
|
25
|
+
import { Easing } from "@thednp/tween";
|
|
26
|
+
|
|
27
|
+
function AnimatedBox() {
|
|
28
|
+
const [styles, tween, configureTween] = useTween({ x: 0, opacity: 1 });
|
|
29
|
+
|
|
30
|
+
setupTween((tw) =>
|
|
31
|
+
tw
|
|
32
|
+
.to({ x: 300, opacity: 0.5 })
|
|
33
|
+
.duration(1.5)
|
|
34
|
+
.easing(Easing.Elastic.Out)
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const handleClick = () => tween.start();
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div
|
|
41
|
+
onClick={handleClick}
|
|
42
|
+
style={{
|
|
43
|
+
translate: `${styles.x}px`,
|
|
44
|
+
opacity: styles.opacity,
|
|
45
|
+
width: 100,
|
|
46
|
+
height: 100,
|
|
47
|
+
background: "blue",
|
|
48
|
+
cursor: "pointer",
|
|
49
|
+
}}
|
|
50
|
+
>
|
|
51
|
+
Click to animate
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
#### Example 2
|
|
58
|
+
|
|
59
|
+
Use React's `useEffect` to configure your Tween - classic React way.
|
|
60
|
+
```tsx
|
|
61
|
+
import { useEffect } from "react";
|
|
62
|
+
import { useTween, Easing } from "@thednp/tween/react";
|
|
63
|
+
|
|
64
|
+
function AnimatedBox() {
|
|
65
|
+
const [styles, tween] = useTween({ x: 0, opacity: 1 });
|
|
66
|
+
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
tween
|
|
69
|
+
.to({ x: 300, opacity: 0.5 })
|
|
70
|
+
.duration(1.5)
|
|
71
|
+
.easing(Easing.Elastic.Out)
|
|
72
|
+
}, []);
|
|
73
|
+
|
|
74
|
+
const handleClick = () => tween.start();
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<div
|
|
78
|
+
onClick={handleClick}
|
|
79
|
+
style={{
|
|
80
|
+
translate: `${styles.x}px`,
|
|
81
|
+
opacity: styles.opacity,
|
|
82
|
+
width: 100,
|
|
83
|
+
height: 100,
|
|
84
|
+
background: "blue",
|
|
85
|
+
cursor: "pointer",
|
|
86
|
+
}}
|
|
87
|
+
>
|
|
88
|
+
Click to animate
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
#### Example 3
|
|
96
|
+
|
|
97
|
+
Override all values on the fly on every `start()` call.
|
|
98
|
+
```tsx
|
|
99
|
+
import { useTween, Easing } from "@thednp/tween/react";
|
|
100
|
+
|
|
101
|
+
function AnimatedBox() {
|
|
102
|
+
const [styles, tween] = useTween({ x: 0, opacity: 1 });
|
|
103
|
+
|
|
104
|
+
const handleClick = () => {
|
|
105
|
+
tween
|
|
106
|
+
.from({ x: 0, opacity: 1 })
|
|
107
|
+
.to({ x: 300, opacity: 0.5 })
|
|
108
|
+
.duration(1.5)
|
|
109
|
+
.easing(Easing.Elastic.Out)
|
|
110
|
+
.start();
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<div
|
|
115
|
+
onClick={handleClick}
|
|
116
|
+
style={{
|
|
117
|
+
translate: `${styles.x}px`,
|
|
118
|
+
opacity: styles.opacity,
|
|
119
|
+
width: 100,
|
|
120
|
+
height: 100,
|
|
121
|
+
background: "blue",
|
|
122
|
+
cursor: "pointer",
|
|
123
|
+
}}
|
|
124
|
+
>
|
|
125
|
+
Click to animate
|
|
126
|
+
</div>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
#### API
|
|
132
|
+
|
|
133
|
+
`useTween(initialValues: T) → [state: T, tween: Tween<T>, configureTween]`
|
|
134
|
+
|
|
135
|
+
* `state` is a React state object object updated on every tween frame
|
|
136
|
+
* `tween` is the persistent `Tween` instance (chain .to(), .duration(), .start(), etc.)
|
|
137
|
+
* `configureTween` is a small utility to register a configure callback for your `Tween` object.
|
|
138
|
+
|
|
139
|
+
Automatically stops on component unmount (via tween.stop())
|
|
140
|
+
|
|
141
|
+
**Notes**
|
|
142
|
+
- Uses a separate mutable object internally → safe for React (no direct state mutation)
|
|
143
|
+
- Shallow copy on update → efficient for flat/nested props (deep clones rare in animations)
|
|
144
|
+
- Supports nested objects via `Tween`'s built-in recursion
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
### useTimeline
|
|
148
|
+
Creates a new [`state`, `timeline`, `configureTimeline`] tuple that updates from current values and updates React's state on every frame.
|
|
149
|
+
|
|
150
|
+
To eliminate the possibility of JANK or values overprocessing, it's **important** that you wrap the configuration of your persistent `Timeline` objects inside wrappers, like shown in the examples below:
|
|
151
|
+
|
|
152
|
+
#### Example 1
|
|
153
|
+
|
|
154
|
+
Create a new `Timeline` and use all available items of the hook - **recommended**.
|
|
155
|
+
```tsx
|
|
156
|
+
import { useTimeline, Easing } from "@thednp/tween/react";
|
|
157
|
+
|
|
158
|
+
function AnimatedSequence() {
|
|
159
|
+
const [pos, timeline, configureTimeline] = useTimeline({ x: 0, y: 0 });
|
|
160
|
+
|
|
161
|
+
configureTimeline((tl) =>
|
|
162
|
+
tl
|
|
163
|
+
.to({ x: 200, duration: 1, easing: Easing.Quad.Out })
|
|
164
|
+
.to({ y: 150, duration: 0.8, easing: Easing.Bounce.Out }, "-=0.5")
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
const playAnim = () => {
|
|
168
|
+
timeline.play();
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<div onClick={playAnim} style={{ translate: `${pos.x}px ${pos.y}px` }}>
|
|
173
|
+
Click to sequence
|
|
174
|
+
</div>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
#### Example 2
|
|
180
|
+
|
|
181
|
+
Use React's `useEffect` to configure your Timeline - classic React way.
|
|
182
|
+
```tsx
|
|
183
|
+
import { useEffect } from "react";
|
|
184
|
+
import { useTimeline, Easing } from "@thednp/tween/react";
|
|
185
|
+
|
|
186
|
+
function AnimatedSequence() {
|
|
187
|
+
const [pos, timeline] = useTimeline({ x: 0, y: 0 });
|
|
188
|
+
|
|
189
|
+
useEffect(() => {
|
|
190
|
+
timeline
|
|
191
|
+
.to({ x: 200, duration: 1, easing: Easing.Quad.Out })
|
|
192
|
+
.to({ y: 150, duration: 0.8, easing: Easing.Bounce.Out }, "-=0.5")
|
|
193
|
+
}, []);
|
|
194
|
+
|
|
195
|
+
const playAnim = () => {
|
|
196
|
+
timeline.play();
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
return (
|
|
200
|
+
<div onClick={playAnim} style={{ translate: `${pos.x}px ${pos.y}px` }}>
|
|
201
|
+
Click to sequence
|
|
202
|
+
</div>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
#### Example 3
|
|
208
|
+
|
|
209
|
+
Override all entries on the fly on every `play()` call.
|
|
210
|
+
```tsx
|
|
211
|
+
import { useTimeline, Easing } from "@thednp/tween/react";
|
|
212
|
+
|
|
213
|
+
function AnimatedSequence() {
|
|
214
|
+
const [pos, timeline] = useTimeline({ x: 0, y: 0 });
|
|
215
|
+
|
|
216
|
+
const playAnim = () => {
|
|
217
|
+
timeline
|
|
218
|
+
.onComplete(() => timeline.clear()) // it's important to clear entries when overriding them over and over
|
|
219
|
+
.to({ x: 200, duration: 1, easing: Easing.Quad.Out })
|
|
220
|
+
.to({ y: 150, duration: 0.8, easing: Easing.Bounce.Out }, "-=0.5")
|
|
221
|
+
.play();
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
return (
|
|
225
|
+
<div onClick={playAnim} style={{ translate: `${pos.x}px ${pos.y}px` }}>
|
|
226
|
+
Click to sequence
|
|
227
|
+
</div>
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Same API notes as `useTween` — persistent instance, auto-cleanup, reactive state.
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
### Which pattern should I use?
|
|
236
|
+
|
|
237
|
+
Any pattern works, it all comes down to your preference, here's why:
|
|
238
|
+
|
|
239
|
+
- **The cleanest**: Use the `configure` callback (third item in the array) — declarative, zero boilerplate, StrictMode-safe.
|
|
240
|
+
- **Classic React style**: Use `useEffect` with empty deps — perfect when config depends on props/state - also StrictMode-safe.
|
|
241
|
+
- **Dynamic/override style**: Call `.to()` / `.from()` directly on the tween/timeline instance inside event handlers — great for user-triggered animations, StrictMode-safe.
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
### Troubleshooting
|
|
245
|
+
|
|
246
|
+
- **Never chain `.to()`, `.duration()`, etc. directly in the component body** without safeguards. This is the most common cause of duplicate/infinite animations in development. Always use one of the three supported patterns above. This is an important note we have to go over because React re-renders everything on every state change, which is why configuring your `Tween` or `Timeline` objects has to be done within either event listeners, the provided `configure` (third item of the returned elements of the hook) or within `useEffect()` callback. Can't stress this enough how important this is, especially with `Timeline` which has the habit of re-adding its entries on every re-render, producing an infinite loop and breaking your React application.
|
|
247
|
+
|
|
248
|
+
**CSS gotchas** — Properties like `top`/`left` require `position`: `absolute`/`relative` on the element and its parent. `transform`/`translate` usually works best for animations.
|
|
249
|
+
|
|
250
|
+
**Updates never happen** - this is due to one of the main factors:
|
|
251
|
+
1) The missmatch of start and end values types, if you're using vanilla JavaScript instead of TypeScript with active linting, you may experience this and not know what happens.
|
|
252
|
+
2) Values are missing due to the incorrect use of lifecycle hooks.
|
|
253
|
+
3) You never called `start()` / `play()`?
|
|
254
|
+
|
|
255
|
+
On a general note, refer to the [README](../README.md) for other tricks and quirks.
|
package/wiki/Solid.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
## SolidJS Primitives for @thednp/tween
|
|
2
|
+
|
|
3
|
+
Leverage SolidJS's fine-grained reactivity with `Tween` and `Timeline` through ultra-lightweight primitives.
|
|
4
|
+
|
|
5
|
+
Unlike the [React hooks](./React.md), these require no additional boilerplate — mutations are tracked automatically, and configuration is free-form.
|
|
6
|
+
|
|
7
|
+
**Note**: These primitives use a lightweight internal `miniStore` optimized for simple flat non-nested numeric objects. For deeply structured or non-numeric state, override `onUpdate` manually to preserve reactivity.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
Don't forget to install.
|
|
11
|
+
```
|
|
12
|
+
npm install @thednp/tween
|
|
13
|
+
# or pnpm/yarn/bun/deno
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### createTween
|
|
17
|
+
|
|
18
|
+
Creates a [store, tween] tuple that animates from current values and updates SolidJS reactive store on every frame.
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
import { createTween } from "@thednp/tween/solid";
|
|
22
|
+
import { Easing } from "@thednp/tween";
|
|
23
|
+
|
|
24
|
+
function AnimatedBox() {
|
|
25
|
+
const [styles, tween] = createTween({ x: 0, opacity: 1 });
|
|
26
|
+
|
|
27
|
+
/** Configure your Tween anywhere, no re-renders ever happen */
|
|
28
|
+
// tween
|
|
29
|
+
// .to({ x: 300, opacity: 0.5 })
|
|
30
|
+
// .duration(1.5)
|
|
31
|
+
// .easing(Easing.Elastic.Out)
|
|
32
|
+
|
|
33
|
+
// Or setup your Tween on the fly
|
|
34
|
+
const handleClick = () => {
|
|
35
|
+
tween
|
|
36
|
+
.from({ x: 0, opacity: 1 })
|
|
37
|
+
.to({ x: 300, opacity: 0.5 })
|
|
38
|
+
.duration(1.5)
|
|
39
|
+
.easing(Easing.Elastic.Out)
|
|
40
|
+
.start();
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div
|
|
45
|
+
onClick={handleClick}
|
|
46
|
+
style={{
|
|
47
|
+
translate: `${styles.x}px`,
|
|
48
|
+
opacity: styles.opacity,
|
|
49
|
+
width: 100,
|
|
50
|
+
height: 100,
|
|
51
|
+
background: "blue",
|
|
52
|
+
cursor: "pointer",
|
|
53
|
+
}}
|
|
54
|
+
>
|
|
55
|
+
Click to animate
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
#### API
|
|
62
|
+
|
|
63
|
+
`createTween(initialValues: T) → [state: T, tween: Tween<T>]`
|
|
64
|
+
|
|
65
|
+
* `state` is a SolidJS store object that updated on every frame
|
|
66
|
+
* `tween` is a `Tween` instance (chain `.to()`, `.duration()`, `.start()`, etc.)
|
|
67
|
+
|
|
68
|
+
Automatically stops on component unmount (via `tween.stop()`).
|
|
69
|
+
|
|
70
|
+
**Notes**
|
|
71
|
+
- Direct mutations are tracked automatically — no manual `onUpdate` or copying needed
|
|
72
|
+
- Works great with nested objects (Tween recursion)
|
|
73
|
+
- For non-numeric or complex state, override `onUpdate` to help preserve reactivity
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
### createTimeline
|
|
77
|
+
|
|
78
|
+
Same pattern for `Timeline` sequencing.
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
import { createTimeline } from "@thednp/tween/solid";
|
|
82
|
+
import { Easing } from "@thednp/tween";
|
|
83
|
+
|
|
84
|
+
function AnimatedSequence() {
|
|
85
|
+
const [pos, timeline] = createTimeline({ x: 0, y: 0 });
|
|
86
|
+
|
|
87
|
+
/** Configure your Timeline anywhere, no re-renders ever happen */
|
|
88
|
+
timeline
|
|
89
|
+
.to({ x: 200, duration: 1, easing: Easing.Quad.Out })
|
|
90
|
+
.to({ y: 150, duration: 0.8, easing: Easing.Bounce.Out }, "-=0.5");
|
|
91
|
+
|
|
92
|
+
const playAnim = () => {
|
|
93
|
+
// or configure your Timeline entries on the fly
|
|
94
|
+
// timeline
|
|
95
|
+
// .onComplete(() => timeline.clear())
|
|
96
|
+
// .to({ x: 200, duration: 1, easing: Easing.Quad.Out })
|
|
97
|
+
// .to({ y: 150, duration: 0.8, easing: Easing.Bounce.Out }, "-=0.5");
|
|
98
|
+
|
|
99
|
+
timeline.play();
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<div onClick={playAnim} style={{ translate: `${pos.x}px ${pos.y}px` }}>
|
|
104
|
+
Click to sequence
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Same API notes as `createTween` — persistent instance, auto-cleanup, reactive state.
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
### Troubleshooting
|
|
114
|
+
|
|
115
|
+
**miniStore limitation** — The internal store is optimized for simple numeric objects (flat and non-nested). For deeply structured or non-numeric state (arrays of objects, strings, etc.), override `onUpdate` manually:
|
|
116
|
+
```ts
|
|
117
|
+
timeline.onUpdate((obj) => {
|
|
118
|
+
// Custom deep merge or setState logic
|
|
119
|
+
setSomeSignalOrStore({ ...obj });
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
Otherwise reactivity may break for untouched properties.
|
|
123
|
+
|
|
124
|
+
The type of values `Tween` and `Timeline` can work with:
|
|
125
|
+
```ts
|
|
126
|
+
type MorphPathSegment =
|
|
127
|
+
| ["M" | "L", number, number]
|
|
128
|
+
| ["C", number, number, number, number, number, number]
|
|
129
|
+
| ["Z"];
|
|
130
|
+
|
|
131
|
+
type MorphPathArray = MorphPathSegment[];
|
|
132
|
+
|
|
133
|
+
type BaseTweenProps = Record<string, number | number[]>;
|
|
134
|
+
type TweenProps = Record<
|
|
135
|
+
string,
|
|
136
|
+
number | number[] | BaseTweenProps | MorphPathArray
|
|
137
|
+
>;
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**CSS gotchas** — Properties like `top`/`left` require `position`: `absolute`/`relative` on the element and its parent. `transform`/`translate` usually works best for animations.
|
|
141
|
+
|
|
142
|
+
**No re-renders** — Unlike React, SolidJS doesn't re-render the whole component on state change — only the accessed reactive properties update. This is why configuration can safely live anywhere.
|
|
143
|
+
|
|
144
|
+
**Updates never happen** - this is due to one of the main factors:
|
|
145
|
+
1) The missmatch of start and end values types, if you're using vanilla JavaScript instead of TypeScript with active linting, you may experience this and not know what happens.
|
|
146
|
+
2) Values are missing due to the incorrect use of lifecycle hooks.
|
|
147
|
+
3) You never called `start()` / `play()`?
|
|
148
|
+
|
|
149
|
+
On a general note, refer to the [README](../README.md) for other tricks and quirks.
|
package/wiki/Timeline.md
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
## Timeline
|
|
2
|
+
|
|
3
|
+
A tiny, ultra-fast `Timeline` tween scheduler. This is simple, just pure flexible chaining similar to `Tween` and stripped to essentials: sequential/overlapping updates, labels, repeat, seek, play/pause/stop/resume. It's a great addition that goes beyond what Tween does and for more complex animations and with far more flexibility and control.
|
|
4
|
+
|
|
5
|
+
Perfect for reactive stores (SolidJS, Svelte, React, etc), SVG/Canvas animations, or anything needing precise control without the overhead.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
- Chainable methods with for best DX;
|
|
10
|
+
* Relative positions (`"+=0"`, `"-=1.5"` offsets, labels);
|
|
11
|
+
- Proper chaining (later entries start from current state);
|
|
12
|
+
- Labels for jumping/seeking;
|
|
13
|
+
- Repeats (including Infinity);
|
|
14
|
+
- Seek anytime (playing or paused, with instant state update);
|
|
15
|
+
- Multiple callbacks: `onStart`, `onUpdate`, `onComplete`, `onStop`, `onPause`, `onResume` (all receive `state`, `progress`);
|
|
16
|
+
- Custom interpolators via `Timeline.use()`;
|
|
17
|
+
- Nested/objects, arrays, custom types supported out-of-box;
|
|
18
|
+
- ~200 lines, blazing fast (while loops for entries);
|
|
19
|
+
* `requestAnimationFrame` loop handled automatically when `.play()` called.
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Usage
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { Timeline, Easing } from '@thednp/tween';
|
|
26
|
+
|
|
27
|
+
// Initial state
|
|
28
|
+
const obj = { x: 0, y: 0, rotate: 0 };
|
|
29
|
+
|
|
30
|
+
// Define Timeline object
|
|
31
|
+
const tl = new Timeline(obj)
|
|
32
|
+
.onUpdate((state, progress) => {
|
|
33
|
+
setState(state) // React/Svelte/store/etc.
|
|
34
|
+
// OR manipulate DOM elements directly
|
|
35
|
+
// make use of [0-1] progress value to update other state
|
|
36
|
+
})
|
|
37
|
+
.label('loopStart', 0) // Back to start (manual "reverse")
|
|
38
|
+
.repeat(Infinity); // OR an INT number
|
|
39
|
+
|
|
40
|
+
// Add entries
|
|
41
|
+
tl
|
|
42
|
+
.to({ x: 100, easing: Easing.Elastic.Out }, '+=1') // 1s to x:100
|
|
43
|
+
.to({ y: 200, rotate: 360, duration: 1.5 }, '-=0.5') // Overlap last 0.5s, rotate full turn in 1.5 sec
|
|
44
|
+
.to({ x: 0, y: 0 }, '+=1'); // Back to original position after 1 sec delay
|
|
45
|
+
|
|
46
|
+
// Start update loop
|
|
47
|
+
tl.play();
|
|
48
|
+
|
|
49
|
+
// Stop update loop
|
|
50
|
+
tl.stop();
|
|
51
|
+
|
|
52
|
+
// Pause update loop
|
|
53
|
+
tl.pause();
|
|
54
|
+
|
|
55
|
+
// Resume update loop
|
|
56
|
+
tl.resume(); // OR tl.start();
|
|
57
|
+
|
|
58
|
+
// Seek
|
|
59
|
+
tl.seek('loopStart'); // Jump to label
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### API
|
|
63
|
+
|
|
64
|
+
#### `new Timeline(initialValues)`
|
|
65
|
+
Creates a new **Timeline** instance targeting the provided object, which means this object is updated during the update runtime.
|
|
66
|
+
|
|
67
|
+
#### `.to(props & config, position?)`
|
|
68
|
+
|
|
69
|
+
Adds a new entry in the Timeline instance.
|
|
70
|
+
|
|
71
|
+
**Parameters**
|
|
72
|
+
* `props & config` allows you to set new prop values we want to change as well as `duration` (in seconds) and `easing` function;
|
|
73
|
+
* `position` allows you to specify a fixed start time (in seconds), a label or a positive (delay) `"+=1"` or negative `"-=1"` offset;
|
|
74
|
+
|
|
75
|
+
#### `.play() / .pause() / .resume() / .stop()`
|
|
76
|
+
Public methods that allow you to start/stop/pause/resume the update. When paused `start()` will also resume.
|
|
77
|
+
|
|
78
|
+
#### `.seek(time | label | relative)`
|
|
79
|
+
A public methods that allows you to jump to a certain point in the update:
|
|
80
|
+
* at a specified *label*,
|
|
81
|
+
* at fixed number of seconds (a number smalled than of the total duration).
|
|
82
|
+
|
|
83
|
+
#### `.repeat(count : number)`
|
|
84
|
+
Allows you to set how many times the update should repeat. You can also use `Infinity`.
|
|
85
|
+
|
|
86
|
+
#### `.label(name, position?)`
|
|
87
|
+
Allows you to register a new label for a given string name and a label or INT number value (less than the total duration).
|
|
88
|
+
|
|
89
|
+
#### Callbacks
|
|
90
|
+
The `.onStart(cb)` / `onUpdate(cb)` / `onComplete(cb)` / `onStop(cb)` / `onPause(cb)` / `onResume(cb)` are a series of public methods that allow you to configure a callback for each invokation: start, stop, update, complete or pause.
|
|
91
|
+
|
|
92
|
+
#### Custom Interpolators
|
|
93
|
+
The `.use(propName: string, interpolationFunction: InterpolatorFunction)` allows you to add custom interpolator functions for your instance.
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
const timeline = new Timeline({ x: 0 });
|
|
97
|
+
|
|
98
|
+
// A regular callback
|
|
99
|
+
timeline.onStart(obj, progress) => {
|
|
100
|
+
console.log("At " + Math.round(progress * 100) + "%, state is", obj);
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
// An update callback has an additional parameter `value`
|
|
104
|
+
timeline.onUpdate((obj, progress) => {
|
|
105
|
+
// update the App state or manipulate the DOM directly
|
|
106
|
+
// progress is a [0-1] value, where 0 is the start and 1 is the end
|
|
107
|
+
console.log("At " + Math.round(progress * 100) + "%, state is", obj);
|
|
108
|
+
})
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
#### `state`, `progress`, `duration`, `isPlaying`, `isPaused`
|
|
112
|
+
A series of getters and properties that reflect the state of the update:
|
|
113
|
+
* the `state` object with all values (not a getter)
|
|
114
|
+
* the [0-1] value `progress` which indicates how much of the `Timeline` update is complete
|
|
115
|
+
* the total `duration` of all the `Timeline` entries
|
|
116
|
+
* `isPlaying` and `isPaused` are *boolean* getters and their returned values reflect the current `Timeline` state.
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
### Custom Interpolators
|
|
120
|
+
|
|
121
|
+
For interpolation of various other object types, `Timeline` allows you to add custom interpolation functions.
|
|
122
|
+
|
|
123
|
+
The package already comes with 2 built in interpolation functions:
|
|
124
|
+
|
|
125
|
+
#### interpolateArray
|
|
126
|
+
This allows you to interpolate arrays for translate/rotate/scape, RGB/RGBA, HSL/HSLA, etc.
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
import { Timeline, interpolateArray } from "@thednp/tween";
|
|
130
|
+
|
|
131
|
+
const target = document.getElementById("my-target");
|
|
132
|
+
const tl = new Timeline({ rgb: [255,0,0] }) // start from red
|
|
133
|
+
// the `rgb` property will now use the custom interpolation
|
|
134
|
+
.use('rgb', interpolateArray);
|
|
135
|
+
// set an update function
|
|
136
|
+
.onUpdate((state) => {
|
|
137
|
+
// update App state or update DOM elements directly
|
|
138
|
+
Object.assign(
|
|
139
|
+
target.style,
|
|
140
|
+
{ "background-color": "rgb(" + state.rgb.join(",") + ")" }),
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// set new value
|
|
144
|
+
tl.to({ rgb: [0,255,0], duration: 1.5 }); // fade to green
|
|
145
|
+
|
|
146
|
+
// start animation
|
|
147
|
+
tl.play();
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
#### interpolatePath
|
|
151
|
+
|
|
152
|
+
This adds SVG morph capability and assumes compatible paths (same segment count/types and coordinate counts — use [svg-path-commander](https://github.com/thednp/svg-path-commander) to process if needed).
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
import { Timeline, interpolatePath } from "@thednp/tween";
|
|
156
|
+
|
|
157
|
+
// Use a fast `PathArray` to string
|
|
158
|
+
// For faster performance use `pathToString` from svg-path-commander
|
|
159
|
+
function pathToString(path: ["M" | "C" | "L", ...number[]][]) {
|
|
160
|
+
return p.map(([c, ...args]) => c + args.join(",")).join(" ");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// target a <path> element
|
|
164
|
+
const path = document.getElementById("my-path");
|
|
165
|
+
|
|
166
|
+
// "M0,0 L600,0 L600,300 L600,600 L0,600 Z"
|
|
167
|
+
const square = [
|
|
168
|
+
["M", 0, 0],
|
|
169
|
+
["L", 600, 0],
|
|
170
|
+
["L", 600, 300], // mid
|
|
171
|
+
["L", 600, 600],
|
|
172
|
+
["L", 0, 600],
|
|
173
|
+
["Z"],
|
|
174
|
+
];
|
|
175
|
+
|
|
176
|
+
// "M0,0 L300,150 L600,300 L300,450 L0,600 Z"
|
|
177
|
+
const triangle = [
|
|
178
|
+
["M", 150, 0],
|
|
179
|
+
["L", 300, 150], // mid
|
|
180
|
+
["L", 450, 300],
|
|
181
|
+
["L", 300, 450], // mid
|
|
182
|
+
["L", 150, 600],
|
|
183
|
+
["Z"],
|
|
184
|
+
];
|
|
185
|
+
|
|
186
|
+
const tl = new Timeline({ path: square })
|
|
187
|
+
.use('path', interpolatePath);
|
|
188
|
+
// you can use any property name you want,
|
|
189
|
+
// `d` might be a good choice as well
|
|
190
|
+
// set an update function
|
|
191
|
+
.onUpdate((state) => {
|
|
192
|
+
// update App state
|
|
193
|
+
// OR update DOM elements directly
|
|
194
|
+
target.setAttribute("d", pathToString(state.path))
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
// set new value
|
|
198
|
+
tl.to({ path: triangle, duration: 1.5 });
|
|
199
|
+
|
|
200
|
+
// start animation
|
|
201
|
+
tl.play();
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Notes**
|
|
205
|
+
* The example provides ready-made `PathArray` objects, they usually require prior preparation manually or using some script to [equalize segments](https://minus-ze.ro/posts/morphing-arbitrary-paths-in-svg/);
|
|
206
|
+
* Continuous `path` updates between multiple shapes requires that **all** path values are compatible, which means they all have same amount of segments and all segments are of the same type (ideal are `[[M, x, y], ...[L, x, y]], ` OR `[[M, x, y], ...[C, cx1, cy1, cx2, cy2, x, y]], `);
|
|
207
|
+
* Our [svg-path-commander](https://github.com/thednp/svg-path-commander/) provides all the tools necessary to process path strings, optimize and even equalize segments (work in progress).
|