@hegemonart/get-design-done 1.16.0 → 1.18.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/.claude-plugin/marketplace.json +7 -5
- package/.claude-plugin/plugin.json +17 -5
- package/CHANGELOG.md +84 -0
- package/README.md +20 -2
- package/agents/design-auditor.md +60 -1
- package/agents/design-doc-writer.md +21 -0
- package/agents/design-executor.md +22 -4
- package/agents/design-pattern-mapper.md +61 -0
- package/agents/motion-mapper.md +74 -9
- package/agents/token-mapper.md +8 -0
- package/package.json +10 -2
- package/reference/components/README.md +27 -23
- package/reference/components/alert.md +198 -0
- package/reference/components/badge.md +202 -0
- package/reference/components/breadcrumbs.md +198 -0
- package/reference/components/chip.md +209 -0
- package/reference/components/command-palette.md +228 -0
- package/reference/components/date-picker.md +227 -0
- package/reference/components/file-upload.md +219 -0
- package/reference/components/list.md +217 -0
- package/reference/components/menu.md +212 -0
- package/reference/components/navbar.md +211 -0
- package/reference/components/pagination.md +205 -0
- package/reference/components/progress.md +210 -0
- package/reference/components/rich-text-editor.md +226 -0
- package/reference/components/sidebar.md +211 -0
- package/reference/components/skeleton.md +197 -0
- package/reference/components/slider.md +208 -0
- package/reference/components/stepper.md +220 -0
- package/reference/components/table.md +229 -0
- package/reference/components/toast.md +200 -0
- package/reference/components/tree.md +225 -0
- package/reference/css-grid-layout.md +835 -0
- package/reference/external/NOTICE.hyperframes +28 -0
- package/reference/image-optimization.md +582 -0
- package/reference/motion-advanced.md +754 -0
- package/reference/motion-easings.md +381 -0
- package/reference/motion-interpolate.md +282 -0
- package/reference/motion-spring.md +234 -0
- package/reference/motion-transition-taxonomy.md +155 -0
- package/reference/motion.md +20 -0
- package/reference/output-contracts/motion-map.schema.json +135 -0
- package/reference/registry.json +183 -0
- package/reference/registry.schema.json +4 -0
- package/reference/variable-fonts-loading.md +532 -0
- package/scripts/lib/easings.cjs +280 -0
- package/scripts/lib/parse-contract.cjs +220 -0
- package/scripts/lib/spring.cjs +160 -0
- package/scripts/tests/test-motion-provenance.sh +64 -0
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
<!-- Source: React Native — Libraries/Animated/Easing.js (MIT License) -->
|
|
2
|
+
<!-- Attribution: Facebook, Inc. and its affiliates — see https://github.com/facebook/react-native/blob/main/Libraries/Animated/Easing.js -->
|
|
3
|
+
|
|
4
|
+
# Motion Easings
|
|
5
|
+
|
|
6
|
+
Canonical easing curve presets derived from React Native's `Easing` module, adapted for CSS and web animation contexts.
|
|
7
|
+
|
|
8
|
+
## Quick Reference
|
|
9
|
+
|
|
10
|
+
| Token | CSS `cubic-bezier` | Character | Settle (spring/bounce) |
|
|
11
|
+
|---|---|---|---|
|
|
12
|
+
| `--ease-linear` | `cubic-bezier(0,0,1,1)` | Constant rate | — |
|
|
13
|
+
| `--ease-quad-in` | `cubic-bezier(0.55,0,1,1)` | Slow start | — |
|
|
14
|
+
| `--ease-quad-out` | `cubic-bezier(0,0,0.45,1)` | Slow end | — |
|
|
15
|
+
| `--ease-quad-in-out` | `cubic-bezier(0.455,0.03,0.515,0.955)` | Slow both | — |
|
|
16
|
+
| `--ease-cubic-in` | `cubic-bezier(0.55,0.055,0.675,0.19)` | Aggressive start | — |
|
|
17
|
+
| `--ease-cubic-out` | `cubic-bezier(0.215,0.61,0.355,1)` | Aggressive end | — |
|
|
18
|
+
| `--ease-cubic-in-out` | `cubic-bezier(0.645,0.045,0.355,1)` | Strong S-curve | — |
|
|
19
|
+
| `--ease-sin-in` | `cubic-bezier(0.47,0,0.745,0.715)` | Gentle start | — |
|
|
20
|
+
| `--ease-sin-out` | `cubic-bezier(0.39,0.575,0.565,1)` | Gentle end | — |
|
|
21
|
+
| `--ease-sin-in-out` | `cubic-bezier(0.445,0.05,0.55,0.95)` | Smooth S-curve | — |
|
|
22
|
+
| `--ease-circle-in` | `cubic-bezier(0.6,0.04,0.98,0.335)` | Circular arc start | — |
|
|
23
|
+
| `--ease-circle-out` | `cubic-bezier(0.075,0.82,0.165,1)` | Circular arc end | — |
|
|
24
|
+
| `--ease-exp-in` | `cubic-bezier(0.95,0.05,0.795,0.035)` | Explosive start | — |
|
|
25
|
+
| `--ease-exp-out` | `cubic-bezier(0.19,1,0.22,1)` | Explosive end | — |
|
|
26
|
+
| `--ease-elastic` | `linear(...)` baked | Overshoot + settle | ~500ms |
|
|
27
|
+
| `--ease-back-in` | `cubic-bezier(0.6,-0.28,0.735,0.045)` | Anticipation | — |
|
|
28
|
+
| `--ease-back-out` | `cubic-bezier(0.175,0.885,0.32,1.275)` | Overshoot | — |
|
|
29
|
+
| `--ease-bounce-out` | `linear(...)` baked | Bounces at end | ~420ms |
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Preset Groups
|
|
34
|
+
|
|
35
|
+
### `linear`
|
|
36
|
+
|
|
37
|
+
Motion at a constant rate. No acceleration or deceleration.
|
|
38
|
+
|
|
39
|
+
- **Human name:** Linear
|
|
40
|
+
- **Character:** Mechanical, robotic. Use only for opacity fades or loading bars where constant rate is intentional.
|
|
41
|
+
- **CSS custom property:** `--ease-linear`
|
|
42
|
+
- **CSS equivalent:** `cubic-bezier(0, 0, 1, 1)` (also the CSS keyword `linear`)
|
|
43
|
+
|
|
44
|
+
```css
|
|
45
|
+
:root {
|
|
46
|
+
--ease-linear: cubic-bezier(0, 0, 1, 1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.fade {
|
|
50
|
+
transition: opacity 200ms var(--ease-linear);
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
// React Native Easing
|
|
56
|
+
Easing.linear
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
### `quad`
|
|
62
|
+
|
|
63
|
+
Quadratic — acceleration proportional to `t²`.
|
|
64
|
+
|
|
65
|
+
- **Human name:** Quad (ease-in-out)
|
|
66
|
+
- **Character:** Subtle, polished. Most common choice for UI transitions.
|
|
67
|
+
- **CSS custom property:** `--ease-quad-in-out`
|
|
68
|
+
|
|
69
|
+
```css
|
|
70
|
+
:root {
|
|
71
|
+
--ease-quad-in: cubic-bezier(0.55, 0, 1, 1);
|
|
72
|
+
--ease-quad-out: cubic-bezier(0, 0, 0.45, 1);
|
|
73
|
+
--ease-quad-in-out: cubic-bezier(0.455, 0.03, 0.515, 0.955);
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```js
|
|
78
|
+
Easing.quad // base: t * t
|
|
79
|
+
Easing.in(Easing.quad)
|
|
80
|
+
Easing.out(Easing.quad)
|
|
81
|
+
Easing.inOut(Easing.quad)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
### `cubic`
|
|
87
|
+
|
|
88
|
+
Cubic — acceleration proportional to `t³`. More pronounced than quad.
|
|
89
|
+
|
|
90
|
+
- **Human name:** Cubic (ease-in-out)
|
|
91
|
+
- **Character:** Snappy, decisive. Good for panels, drawers, menus.
|
|
92
|
+
- **CSS custom property:** `--ease-cubic-in-out`
|
|
93
|
+
|
|
94
|
+
```css
|
|
95
|
+
:root {
|
|
96
|
+
--ease-cubic-in: cubic-bezier(0.55, 0.055, 0.675, 0.19);
|
|
97
|
+
--ease-cubic-out: cubic-bezier(0.215, 0.61, 0.355, 1);
|
|
98
|
+
--ease-cubic-out: cubic-bezier(0.215, 0.61, 0.355, 1); /* "ease-out-cubic" */
|
|
99
|
+
--ease-cubic-in-out: cubic-bezier(0.645, 0.045, 0.355, 1);
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
```js
|
|
104
|
+
Easing.cubic // base: t * t * t
|
|
105
|
+
Easing.inOut(Easing.cubic)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
### `poly(n)`
|
|
111
|
+
|
|
112
|
+
Generalised polynomial — `t^n`. Quad is `poly(2)`, cubic is `poly(3)`.
|
|
113
|
+
|
|
114
|
+
- **Human name:** Poly-n
|
|
115
|
+
- **Character:** Tunable. Higher `n` = harder snap.
|
|
116
|
+
- **CSS custom property:** No single token; bake to `cubic-bezier` per n.
|
|
117
|
+
|
|
118
|
+
```js
|
|
119
|
+
Easing.poly(4) // quartic
|
|
120
|
+
Easing.poly(5) // quintic
|
|
121
|
+
Easing.in(Easing.poly(4))
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
No direct CSS equivalent — approximate with a `cubic-bezier` or bake to `linear()`.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
### `sin`
|
|
129
|
+
|
|
130
|
+
Sinusoidal — easing shaped by the sine function. Smooth and natural.
|
|
131
|
+
|
|
132
|
+
- **Human name:** Sine
|
|
133
|
+
- **Character:** Organic, gentle. Works well for ambient or breathing animations.
|
|
134
|
+
- **CSS custom property:** `--ease-sin-in-out`
|
|
135
|
+
|
|
136
|
+
```css
|
|
137
|
+
:root {
|
|
138
|
+
--ease-sin-in: cubic-bezier(0.47, 0, 0.745, 0.715);
|
|
139
|
+
--ease-sin-out: cubic-bezier(0.39, 0.575, 0.565, 1);
|
|
140
|
+
--ease-sin-in-out: cubic-bezier(0.445, 0.05, 0.55, 0.95);
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
```js
|
|
145
|
+
Easing.sin
|
|
146
|
+
// Internally: 1 - Math.cos(t * Math.PI / 2)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
### `circle`
|
|
152
|
+
|
|
153
|
+
Circular arc — based on `sqrt(1 - t²)`. Sharp acceleration at the end of its range.
|
|
154
|
+
|
|
155
|
+
- **Human name:** Circ
|
|
156
|
+
- **Character:** Abrupt, dramatic. Use sparingly for emphasis.
|
|
157
|
+
- **CSS custom property:** `--ease-circle-out`
|
|
158
|
+
|
|
159
|
+
```css
|
|
160
|
+
:root {
|
|
161
|
+
--ease-circle-in: cubic-bezier(0.6, 0.04, 0.98, 0.335);
|
|
162
|
+
--ease-circle-out: cubic-bezier(0.075, 0.82, 0.165, 1);
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
```js
|
|
167
|
+
Easing.circle
|
|
168
|
+
// Internally: 1 - Math.sqrt(1 - t * t)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
### `exp`
|
|
174
|
+
|
|
175
|
+
Exponential — `2^(10 * (t - 1))`. Starts near-zero, ends explosively.
|
|
176
|
+
|
|
177
|
+
- **Human name:** Expo
|
|
178
|
+
- **Character:** High-impact. Good for reveal animations, hero entrances.
|
|
179
|
+
- **CSS custom property:** `--ease-exp-out`
|
|
180
|
+
|
|
181
|
+
```css
|
|
182
|
+
:root {
|
|
183
|
+
--ease-exp-in: cubic-bezier(0.95, 0.05, 0.795, 0.035);
|
|
184
|
+
--ease-exp-out: cubic-bezier(0.19, 1, 0.22, 1);
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
```js
|
|
189
|
+
Easing.exp
|
|
190
|
+
// Internally: Math.pow(2, 10 * (t - 1))
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
### `elastic(bounciness, speed)`
|
|
196
|
+
|
|
197
|
+
Spring-like oscillation past the target before settling.
|
|
198
|
+
|
|
199
|
+
- **Human name:** Elastic
|
|
200
|
+
- **Character:** Playful, bouncy. Overshoots final value one or more times.
|
|
201
|
+
- **Default params:** `bounciness = 1`, `speed = 1`
|
|
202
|
+
- **60fps settle time:** ~500ms at defaults
|
|
203
|
+
- **CSS custom property:** `--ease-elastic` (must be baked to `linear()`)
|
|
204
|
+
|
|
205
|
+
```css
|
|
206
|
+
/* Baked approximation for CSS linear() — default elastic */
|
|
207
|
+
:root {
|
|
208
|
+
--ease-elastic-out: linear(
|
|
209
|
+
0, 0.009, 0.035 2.1%, 0.141, 0.281 6.7%, 0.723 12.9%,
|
|
210
|
+
0.938, 1.017, 1.048, 1.056, 1.046 20.1%, 0.999 24.3%,
|
|
211
|
+
0.984, 0.983, 0.988 29.9%, 1.001 35.5%, 1.004 40.1%, 1 100%
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
```js
|
|
217
|
+
Easing.elastic(1, 1) // default
|
|
218
|
+
Easing.out(Easing.elastic(1, 1))
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**Parameter guide:**
|
|
222
|
+
- `bounciness`: amplitude of overshoot (0 = no overshoot, higher = more)
|
|
223
|
+
- `speed`: controls oscillation frequency (higher = faster settle)
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
### `back(s)`
|
|
228
|
+
|
|
229
|
+
Anticipation curve — slightly retracts before moving forward (or overshoots then settles).
|
|
230
|
+
|
|
231
|
+
- **Human name:** Back
|
|
232
|
+
- **Character:** Mechanical delight. Communicates intentionality.
|
|
233
|
+
- **Default param:** `s = 1.70158`
|
|
234
|
+
- **CSS custom property:** `--ease-back-out`
|
|
235
|
+
|
|
236
|
+
```css
|
|
237
|
+
:root {
|
|
238
|
+
/* s = 1.70158 (default) */
|
|
239
|
+
--ease-back-in: cubic-bezier(0.6, -0.28, 0.735, 0.045);
|
|
240
|
+
--ease-back-out: cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
```js
|
|
245
|
+
Easing.back(1.70158) // default overshoot
|
|
246
|
+
Easing.out(Easing.back(2.5)) // stronger overshoot
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
### `bounce`
|
|
252
|
+
|
|
253
|
+
Simulates a physical bounce — the value bounces off the endpoint.
|
|
254
|
+
|
|
255
|
+
- **Human name:** Bounce
|
|
256
|
+
- **Character:** Playful, energetic. Use for game-like or informal UIs.
|
|
257
|
+
- **60fps settle time:** ~420ms
|
|
258
|
+
- **CSS custom property:** `--ease-bounce-out` (must be baked to `linear()`)
|
|
259
|
+
|
|
260
|
+
```css
|
|
261
|
+
:root {
|
|
262
|
+
/* Baked CSS linear() approximation */
|
|
263
|
+
--ease-bounce-out: linear(
|
|
264
|
+
0, 0.004, 0.016, 0.035, 0.063, 0.098, 0.141, 0.191, 0.25,
|
|
265
|
+
0.316, 0.391, 0.469, 0.563, 0.656, 0.765, 0.875, 0.891,
|
|
266
|
+
0.906 45.7%, 0.922, 0.938, 0.953 50%, 0.984, 1.016, 1.031,
|
|
267
|
+
1.047, 1.063, 1.016, 1 100%
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
```js
|
|
273
|
+
Easing.bounce
|
|
274
|
+
// Internally: piecewise quadratic with 4 bounces
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
### `bezier(x1, y1, x2, y2)`
|
|
280
|
+
|
|
281
|
+
Raw cubic Bézier — the same primitive that powers all CSS `cubic-bezier()` calls.
|
|
282
|
+
|
|
283
|
+
- **Human name:** Custom Bézier
|
|
284
|
+
- **Character:** Whatever you specify.
|
|
285
|
+
|
|
286
|
+
```js
|
|
287
|
+
Easing.bezier(0.25, 0.1, 0.25, 1.0) // CSS ease
|
|
288
|
+
Easing.bezier(0.42, 0, 1, 1) // CSS ease-in
|
|
289
|
+
Easing.bezier(0, 0, 0.58, 1) // CSS ease-out
|
|
290
|
+
Easing.bezier(0.42, 0, 0.58, 1) // CSS ease-in-out
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
```css
|
|
294
|
+
/* Direct mapping */
|
|
295
|
+
transition-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1.0);
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## Higher-Order Wrappers: `in`, `out`, `inOut`
|
|
301
|
+
|
|
302
|
+
React Native's `Easing` module exposes three composition functions that transform any base curve into its directional variant. This is the correct pattern for producing all six canonical easing directions from any single base function.
|
|
303
|
+
|
|
304
|
+
### How it works
|
|
305
|
+
|
|
306
|
+
Given a base easing function `f(t)` where `t ∈ [0, 1]`:
|
|
307
|
+
|
|
308
|
+
| Wrapper | Formula | Effect |
|
|
309
|
+
|---|---|---|
|
|
310
|
+
| `in(f)` | `f(t)` | Acceleration at start — slow → fast |
|
|
311
|
+
| `out(f)` | `1 - f(1 - t)` | Deceleration at end — fast → slow |
|
|
312
|
+
| `inOut(f)` | `t < 0.5 ? f(2t)/2 : 1 - f(2(1-t))/2` | Slow start and end |
|
|
313
|
+
|
|
314
|
+
### Examples
|
|
315
|
+
|
|
316
|
+
```js
|
|
317
|
+
import { in as easeIn, out as easeOut, inOut, cubic } from './lib/easings.cjs';
|
|
318
|
+
|
|
319
|
+
const easeInCubic = easeIn(cubic); // t => t³
|
|
320
|
+
const easeOutCubic = easeOut(cubic); // t => 1 - (1-t)³
|
|
321
|
+
const easeInOutCubic = inOut(cubic); // smooth S-curve
|
|
322
|
+
|
|
323
|
+
// Works identically with any base:
|
|
324
|
+
const easeInBounce = easeIn(bounce);
|
|
325
|
+
const easeInElastic = easeIn(elastic(1, 1));
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
```css
|
|
329
|
+
/* CSS does the same thing natively for cubic-bezier: */
|
|
330
|
+
/* ease-in = cubic-bezier(x1,y1, x2,y2) with control points in lower-left */
|
|
331
|
+
/* ease-out = cubic-bezier(x1,y1, x2,y2) with control points in upper-right */
|
|
332
|
+
/* inOut = symmetric control points */
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### Design rule
|
|
336
|
+
|
|
337
|
+
- Use `out` for **enter** transitions (element arrives, decelerates into place).
|
|
338
|
+
- Use `in` for **exit** transitions (element departs, accelerates away).
|
|
339
|
+
- Use `inOut` for **state changes** (element moves from one state to another, both ends cushioned).
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## CSS Custom Properties — Full Token Set
|
|
344
|
+
|
|
345
|
+
```css
|
|
346
|
+
:root {
|
|
347
|
+
/* Linear */
|
|
348
|
+
--ease-linear: cubic-bezier(0, 0, 1, 1);
|
|
349
|
+
|
|
350
|
+
/* Sine */
|
|
351
|
+
--ease-sin-in: cubic-bezier(0.47, 0, 0.745, 0.715);
|
|
352
|
+
--ease-sin-out: cubic-bezier(0.39, 0.575, 0.565, 1);
|
|
353
|
+
--ease-sin-in-out: cubic-bezier(0.445, 0.05, 0.55, 0.95);
|
|
354
|
+
|
|
355
|
+
/* Quad */
|
|
356
|
+
--ease-quad-in: cubic-bezier(0.55, 0, 1, 1);
|
|
357
|
+
--ease-quad-out: cubic-bezier(0, 0, 0.45, 1);
|
|
358
|
+
--ease-quad-in-out: cubic-bezier(0.455, 0.03, 0.515, 0.955);
|
|
359
|
+
|
|
360
|
+
/* Cubic */
|
|
361
|
+
--ease-cubic-in: cubic-bezier(0.55, 0.055, 0.675, 0.19);
|
|
362
|
+
--ease-cubic-out: cubic-bezier(0.215, 0.61, 0.355, 1);
|
|
363
|
+
--ease-cubic-in-out: cubic-bezier(0.645, 0.045, 0.355, 1);
|
|
364
|
+
|
|
365
|
+
/* Circ */
|
|
366
|
+
--ease-circle-in: cubic-bezier(0.6, 0.04, 0.98, 0.335);
|
|
367
|
+
--ease-circle-out: cubic-bezier(0.075, 0.82, 0.165, 1);
|
|
368
|
+
|
|
369
|
+
/* Expo */
|
|
370
|
+
--ease-exp-in: cubic-bezier(0.95, 0.05, 0.795, 0.035);
|
|
371
|
+
--ease-exp-out: cubic-bezier(0.19, 1, 0.22, 1);
|
|
372
|
+
|
|
373
|
+
/* Back (default s=1.70158) */
|
|
374
|
+
--ease-back-in: cubic-bezier(0.6, -0.28, 0.735, 0.045);
|
|
375
|
+
--ease-back-out: cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
|
376
|
+
|
|
377
|
+
/* Elastic and Bounce — baked linear() — see above for full values */
|
|
378
|
+
--ease-elastic-out: linear(0, 0.009, 0.035 2.1%, 0.141, 0.281 6.7%, 0.723 12.9%, 0.938, 1.017, 1.048, 1.056, 1.046 20.1%, 0.999 24.3%, 0.984, 0.983, 0.988 29.9%, 1.001 35.5%, 1.004 40.1%, 1 100%);
|
|
379
|
+
--ease-bounce-out: linear(0, 0.004, 0.016, 0.035, 0.063, 0.098, 0.141, 0.191, 0.250, 0.316, 0.391, 0.469, 0.563, 0.656, 0.765, 0.875, 0.891, 0.906 45.7%, 0.922, 0.938, 0.953 50%, 0.984, 1.016, 1.031, 1.047, 1.063, 1.016, 1 100%);
|
|
380
|
+
}
|
|
381
|
+
```
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
<!-- Source: React Native — Libraries/Animated/AnimatedInterpolation.js (MIT License) -->
|
|
2
|
+
<!-- Attribution: Facebook, Inc. and its affiliates -->
|
|
3
|
+
|
|
4
|
+
# Motion Interpolation
|
|
5
|
+
|
|
6
|
+
Interpolation is the mechanism that maps any changing value — progress, scroll position, pointer delta, elapsed time — into any animated output range. Every animation in a system can be decomposed into a single canonical form.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Core Concept
|
|
11
|
+
|
|
12
|
+
Any animation decomposes into four parameters:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
interpolate(inputValue, inputRange, outputRange, extrapolationConfig?)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
| Parameter | Type | Description |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| `inputValue` | `number` | The driver value (a scalar that changes over time) |
|
|
21
|
+
| `inputRange` | `number[]` | Ordered list of input breakpoints |
|
|
22
|
+
| `outputRange` | `number \| string[]` | Corresponding output values at each breakpoint |
|
|
23
|
+
| `extrapolationConfig` | `object?` | What to do when input is outside `inputRange` |
|
|
24
|
+
|
|
25
|
+
Between any two adjacent breakpoints, the output is linearly interpolated by default. An `easing` function can be applied per-segment to shape the curve. See [motion-easings.md](./motion-easings.md) for the `easing` parameter.
|
|
26
|
+
|
|
27
|
+
**Principle:** The driver value is always a plain number. The animation is a pure function of that number. This separates *what drives the animation* from *what the animation does*, making it composable and testable.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Extrapolation Modes
|
|
32
|
+
|
|
33
|
+
When `inputValue` falls outside `inputRange`, the behavior is controlled by the extrapolation config. React Native defines four named modes:
|
|
34
|
+
|
|
35
|
+
### `extend` (default)
|
|
36
|
+
|
|
37
|
+
Continues the linear trend of the first/last segment past the range boundary. Output keeps growing (or shrinking) indefinitely.
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
interpolate(scrollY, [0, 100], [0, 1], { extrapolate: 'extend' })
|
|
41
|
+
// scrollY = 200 → output = 2.0
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Use when:** The animation should track the input continuously beyond the defined range (e.g., parallax that keeps moving as you scroll past a section).
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
### `clamp`
|
|
49
|
+
|
|
50
|
+
Pins the output to the boundary value once the input leaves the range.
|
|
51
|
+
|
|
52
|
+
```js
|
|
53
|
+
interpolate(scrollY, [0, 100], [0, 1], { extrapolate: 'clamp' })
|
|
54
|
+
// scrollY = 200 → output = 1.0 (clamped at top)
|
|
55
|
+
// scrollY = -50 → output = 0.0 (clamped at bottom)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Use when:** An element should be fully visible/hidden after a threshold and not go further (e.g., a header that fades in and stays visible after scrolling 100px).
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
### `identity`
|
|
63
|
+
|
|
64
|
+
Returns the raw input value itself once outside the range, ignoring the output mapping.
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
interpolate(t, [0, 1], [0, 100], { extrapolate: 'identity' })
|
|
68
|
+
// t = 1.5 → output = 1.5 (not 150 — raw input echoed)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Use when:** You need the value to revert to unscaled behavior outside a zone. Uncommon; most interpolation uses `clamp` or `extend`.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
### `wrap`
|
|
76
|
+
|
|
77
|
+
Wraps the input cyclically around the input range before applying the mapping. Produces looping output.
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
interpolate(t, [0, 1], [0, 360], { extrapolate: 'wrap' })
|
|
81
|
+
// t = 1.5 → maps to t=0.5 → output = 180
|
|
82
|
+
// t = 2.0 → maps to t=0.0 → output = 0
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Use when:** Rotation, looping progress indicators, repeating carousel positions.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Taxonomy of Animation Types
|
|
90
|
+
|
|
91
|
+
### Progress-linked (`0 → 1`)
|
|
92
|
+
|
|
93
|
+
The driver is a normalized progress value from 0 to 1. Most explicit animations and transitions fall here.
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
inputRange: [0, 1]
|
|
97
|
+
outputRange: [startValue, endValue]
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Examples:** mount/unmount transition, tab indicator position, wizard step progress.
|
|
101
|
+
|
|
102
|
+
```js
|
|
103
|
+
// Framer Motion
|
|
104
|
+
const opacity = useTransform(progress, [0, 1], [0, 1]);
|
|
105
|
+
const x = useTransform(progress, [0, 1], ['-100%', '0%']);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
### Scroll-linked (px scroll position)
|
|
111
|
+
|
|
112
|
+
The driver is the raw pixel scroll offset from a scroll container. Common range is `[0, containerHeight]` or a sub-range for reveal effects.
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
inputRange: [0, 300] // px from top of scroll container
|
|
116
|
+
outputRange: [1, 0] // opacity fades out as user scrolls 300px
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
```js
|
|
120
|
+
// Framer Motion — scroll-linked opacity
|
|
121
|
+
const { scrollY } = useScroll();
|
|
122
|
+
const opacity = useTransform(scrollY, [0, 300], [1, 0], { clamp: true });
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
```css
|
|
126
|
+
/* CSS Scroll Timeline equivalent */
|
|
127
|
+
@keyframes fade-header {
|
|
128
|
+
from { opacity: 1; }
|
|
129
|
+
to { opacity: 0; }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.header {
|
|
133
|
+
animation: fade-header linear both;
|
|
134
|
+
animation-timeline: scroll(root);
|
|
135
|
+
animation-range: 0px 300px;
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
### Gesture-linked (pointer delta)
|
|
142
|
+
|
|
143
|
+
The driver is the cumulative pointer displacement in pixels (e.g., drag distance). Input range is typically anchored at 0 with a resistance model at the extremes.
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
inputRange: [-200, 0, 200]
|
|
147
|
+
outputRange: ['-100%', '0%', '100%']
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Key difference from scroll-linked:** gesture-linked animations are often bidirectional and involve velocity projection for throw/release behavior.
|
|
151
|
+
|
|
152
|
+
```js
|
|
153
|
+
// Framer Motion — drag with spring release
|
|
154
|
+
<motion.div
|
|
155
|
+
drag="x"
|
|
156
|
+
dragConstraints={{ left: -200, right: 200 }}
|
|
157
|
+
style={{ x: dragX }}
|
|
158
|
+
/>
|
|
159
|
+
|
|
160
|
+
// Manual lerp pattern for custom gesture handling
|
|
161
|
+
function lerp(a, b, t) {
|
|
162
|
+
return a + (b - a) * t;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function interpolateGesture(delta, inputRange, outputRange) {
|
|
166
|
+
const [inMin, inMax] = inputRange;
|
|
167
|
+
const [outMin, outMax] = outputRange;
|
|
168
|
+
const t = Math.max(0, Math.min(1, (delta - inMin) / (inMax - inMin)));
|
|
169
|
+
return lerp(outMin, outMax, t);
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
### Time-linked (`Date.now()`)
|
|
176
|
+
|
|
177
|
+
The driver is wall-clock time in milliseconds. Used for ambient animations, looping effects, and time-based sequencing where a spring or tween is not driving the value.
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
inputRange: [startTime, startTime + durationMs]
|
|
181
|
+
outputRange: [0, 1]
|
|
182
|
+
extrapolate: 'clamp'
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
```js
|
|
186
|
+
// Manual time-linked animation loop
|
|
187
|
+
function useTimeLinked(durationMs) {
|
|
188
|
+
const [progress, setProgress] = useState(0);
|
|
189
|
+
const startTime = useRef(Date.now());
|
|
190
|
+
|
|
191
|
+
useAnimationFrame(() => {
|
|
192
|
+
const elapsed = Date.now() - startTime.current;
|
|
193
|
+
const t = Math.min(1, elapsed / durationMs);
|
|
194
|
+
setProgress(t);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
return progress;
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Using Easing with Interpolation
|
|
204
|
+
|
|
205
|
+
The `easing` parameter shapes the interpolation curve within a segment. It accepts any `t → value` function from [motion-easings.md](./motion-easings.md).
|
|
206
|
+
|
|
207
|
+
```js
|
|
208
|
+
import { inOut, cubic } from '../scripts/lib/easings.cjs';
|
|
209
|
+
|
|
210
|
+
// Framer Motion with easing
|
|
211
|
+
const y = useTransform(scrollY, [0, 400], [0, -100], {
|
|
212
|
+
ease: inOut(cubic), // or any easing function
|
|
213
|
+
});
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
```css
|
|
217
|
+
/* CSS animation-timing-function applies per-keyframe segment */
|
|
218
|
+
@keyframes slide-up {
|
|
219
|
+
from { transform: translateY(0); animation-timing-function: var(--ease-cubic-out); }
|
|
220
|
+
to { transform: translateY(-100px); }
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Manual `lerp` Pattern
|
|
227
|
+
|
|
228
|
+
When you need to interpolate outside a framework, the bare pattern is:
|
|
229
|
+
|
|
230
|
+
```js
|
|
231
|
+
/**
|
|
232
|
+
* Linear interpolation between two values.
|
|
233
|
+
* @param {number} a - Start value (at t=0)
|
|
234
|
+
* @param {number} b - End value (at t=1)
|
|
235
|
+
* @param {number} t - Progress in [0, 1]
|
|
236
|
+
* @returns {number}
|
|
237
|
+
*/
|
|
238
|
+
function lerp(a, b, t) {
|
|
239
|
+
return a + (b - a) * t;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Map inputValue from inputRange to outputRange with optional easing and clamping.
|
|
244
|
+
*/
|
|
245
|
+
function interpolate(inputValue, [inMin, inMax], [outMin, outMax], {
|
|
246
|
+
easing = (t) => t,
|
|
247
|
+
extrapolate = 'clamp',
|
|
248
|
+
} = {}) {
|
|
249
|
+
let t = (inputValue - inMin) / (inMax - inMin);
|
|
250
|
+
|
|
251
|
+
if (extrapolate === 'clamp') t = Math.max(0, Math.min(1, t));
|
|
252
|
+
// extend: t is unbounded
|
|
253
|
+
// identity: return inputValue if out of range
|
|
254
|
+
// wrap: t = ((t % 1) + 1) % 1
|
|
255
|
+
|
|
256
|
+
return lerp(outMin, outMax, easing(t));
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Multi-segment Interpolation
|
|
263
|
+
|
|
264
|
+
Passing more than two breakpoints produces piecewise interpolation. Each adjacent pair is a separate segment.
|
|
265
|
+
|
|
266
|
+
```js
|
|
267
|
+
// Framer Motion multi-segment
|
|
268
|
+
const background = useTransform(
|
|
269
|
+
scrollY,
|
|
270
|
+
[0, 200, 400, 600],
|
|
271
|
+
['#ffffff', '#f0f0f0', '#e0e0e0', '#000000']
|
|
272
|
+
);
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
This is equivalent to three separate interpolations chained by range.
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## Cross-references
|
|
280
|
+
|
|
281
|
+
- Easing functions for the `easing` / `ease` parameter: [motion-easings.md](./motion-easings.md)
|
|
282
|
+
- Spring-based animation (an alternative driver to progress/scroll): [motion-spring.md](./motion-spring.md)
|