motion 13.1.1 → 13.2.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/dist/cjs/three.js +192 -0
- package/dist/cjs/vgpu.js +248 -0
- package/dist/es/three.mjs +188 -0
- package/dist/es/vgpu.mjs +244 -0
- package/dist/motion.dev.js +150 -86
- package/dist/motion.js +1 -1
- package/dist/three.d.ts +29 -0
- package/dist/vgpu.d.ts +25 -0
- package/package.json +16 -3
package/dist/es/vgpu.mjs
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { createEffect, frame, rgba, color, clamp, hslaToRgba } from 'framer-motion/dom';
|
|
2
|
+
|
|
3
|
+
const axes = "xyzw";
|
|
4
|
+
const radiansPerDegree = Math.PI / 180;
|
|
5
|
+
const transforms = {
|
|
6
|
+
x: ["position", 0],
|
|
7
|
+
y: ["position", 1],
|
|
8
|
+
z: ["position", 2],
|
|
9
|
+
rotateX: ["rotation", 0, true],
|
|
10
|
+
rotateY: ["rotation", 1, true],
|
|
11
|
+
rotateZ: ["rotation", 2, true],
|
|
12
|
+
scaleX: ["scale", 0],
|
|
13
|
+
scaleY: ["scale", 1],
|
|
14
|
+
scaleZ: ["scale", 2],
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Last vector written to each path. vgpu subjects like Effect and
|
|
18
|
+
* SharedUniforms have no getters, and SceneNode stores rotation as a
|
|
19
|
+
* quaternion, so this is how single components find their siblings.
|
|
20
|
+
*/
|
|
21
|
+
const shadows = new WeakMap();
|
|
22
|
+
/**
|
|
23
|
+
* Values changed this frame, flushed into one set() per subject.
|
|
24
|
+
*/
|
|
25
|
+
const pending = new Map();
|
|
26
|
+
function flush() {
|
|
27
|
+
pending.forEach(applyValues);
|
|
28
|
+
pending.clear();
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Binds motion values to vgpu shared uniforms, Effect/Draw/Compute bindings
|
|
32
|
+
* ("params.time"), scene nodes (x, rotateY, scale), cameras, lights,
|
|
33
|
+
* materials, orbit controls and target clear colors.
|
|
34
|
+
*
|
|
35
|
+
* Register with `animate.addEffect(vgpuEffect)` so `animate()` can target
|
|
36
|
+
* these subjects directly, or call it yourself to wire up existing motion
|
|
37
|
+
* values:
|
|
38
|
+
*
|
|
39
|
+
* ```ts
|
|
40
|
+
* vgpuEffect(wave, { "params.time": time })
|
|
41
|
+
* vgpuEffect(cube, { x, rotateY })
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* Changed values are batched into a single set() per subject per frame in
|
|
45
|
+
* `frame.preRender`, ahead of render loops scheduled with `frame.render`.
|
|
46
|
+
*/
|
|
47
|
+
const vgpuEffect = createEffect((subject, state, key, value) => state.set(key, value, () => {
|
|
48
|
+
const bag = pending.get(subject) ?? {};
|
|
49
|
+
bag[key] = state.latest[key];
|
|
50
|
+
pending.set(subject, bag);
|
|
51
|
+
frame.preRender(flush, false, true);
|
|
52
|
+
}, undefined, false), {
|
|
53
|
+
test: isVGPUSubject,
|
|
54
|
+
read: readInitial,
|
|
55
|
+
step: frame.preRender,
|
|
56
|
+
});
|
|
57
|
+
/**
|
|
58
|
+
* Claims vgpu shader units (Effect, Draw, Compute), shared uniforms, scene
|
|
59
|
+
* nodes, materials, orbit controls and targets. Everything else, including
|
|
60
|
+
* Three.js vectors and plain objects, is left alone.
|
|
61
|
+
*/
|
|
62
|
+
function isVGPUSubject(subject) {
|
|
63
|
+
if (!subject || typeof subject !== "object")
|
|
64
|
+
return false;
|
|
65
|
+
const s = subject;
|
|
66
|
+
return typeof s.set === "function"
|
|
67
|
+
? typeof s.kind === "string" ||
|
|
68
|
+
"gpu" in s ||
|
|
69
|
+
"reflection" in s ||
|
|
70
|
+
typeof s.yaw === "number"
|
|
71
|
+
: "clearColor" in s && "gpu" in s;
|
|
72
|
+
}
|
|
73
|
+
function isNode(subject) {
|
|
74
|
+
return "quaternion" in subject;
|
|
75
|
+
}
|
|
76
|
+
function isVector(value) {
|
|
77
|
+
return Array.isArray(value) || ArrayBuffer.isView(value);
|
|
78
|
+
}
|
|
79
|
+
function resolveKey(subject, key) {
|
|
80
|
+
const transform = isNode(subject) && transforms[key];
|
|
81
|
+
if (transform) {
|
|
82
|
+
return {
|
|
83
|
+
path: [transform[0]],
|
|
84
|
+
index: transform[1],
|
|
85
|
+
degrees: transform[2],
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
const path = key.split(".");
|
|
89
|
+
const last = path[path.length - 1];
|
|
90
|
+
const index = axes.indexOf(last.slice(-1).toLowerCase());
|
|
91
|
+
if (last.length > 1 && index !== -1) {
|
|
92
|
+
const base = [...path.slice(0, -1), last.slice(0, -1)];
|
|
93
|
+
if (getVector(subject, base))
|
|
94
|
+
return { path: base, index };
|
|
95
|
+
}
|
|
96
|
+
return { path };
|
|
97
|
+
}
|
|
98
|
+
function readPath(subject, path) {
|
|
99
|
+
return path.reduce((value, key) => value?.[key], subject);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Reads a subject property, falling back to ShaderMaterial's `values` record.
|
|
103
|
+
*/
|
|
104
|
+
function readValue(subject, path) {
|
|
105
|
+
return (readPath(subject, path) ??
|
|
106
|
+
(subject.values && readPath(subject.values, path)));
|
|
107
|
+
}
|
|
108
|
+
function getVector(subject, path) {
|
|
109
|
+
const value = readValue(subject, path);
|
|
110
|
+
if (isVector(value))
|
|
111
|
+
return Array.from(value);
|
|
112
|
+
const shadow = shadows.get(subject)?.[path.join(".")];
|
|
113
|
+
if (shadow)
|
|
114
|
+
return shadow;
|
|
115
|
+
return path[0] === "rotation" && isNode(subject)
|
|
116
|
+
? eulerFromQuaternion(subject.quaternion)
|
|
117
|
+
: undefined;
|
|
118
|
+
}
|
|
119
|
+
function setShadow(subject, path, vector) {
|
|
120
|
+
const shadow = shadows.get(subject) ?? {};
|
|
121
|
+
shadow[path.join(".")] = vector;
|
|
122
|
+
shadows.set(subject, shadow);
|
|
123
|
+
}
|
|
124
|
+
function setPath(target, path, value) {
|
|
125
|
+
var _a;
|
|
126
|
+
const last = path.length - 1;
|
|
127
|
+
let current = target;
|
|
128
|
+
for (let i = 0; i < last; i++) {
|
|
129
|
+
current = current[_a = path[i]] ?? (current[_a] = {});
|
|
130
|
+
}
|
|
131
|
+
current[path[last]] = value;
|
|
132
|
+
}
|
|
133
|
+
function isColorTarget(target) {
|
|
134
|
+
const sample = Array.isArray(target)
|
|
135
|
+
? target.find((value) => typeof value === "string")
|
|
136
|
+
: target;
|
|
137
|
+
return typeof sample === "string" && color.test(sample);
|
|
138
|
+
}
|
|
139
|
+
function readInitial(subject, key, target) {
|
|
140
|
+
const { path, index, degrees } = resolveKey(subject, key);
|
|
141
|
+
if (index !== undefined) {
|
|
142
|
+
const value = getVector(subject, path)?.[index];
|
|
143
|
+
return degrees && value !== undefined ? value / radiansPerDegree : value;
|
|
144
|
+
}
|
|
145
|
+
const value = readValue(subject, path);
|
|
146
|
+
if (typeof value === "number")
|
|
147
|
+
return value;
|
|
148
|
+
if (!isVector(value))
|
|
149
|
+
return undefined;
|
|
150
|
+
if (isColorTarget(target))
|
|
151
|
+
return toColorString(value);
|
|
152
|
+
const sample = Array.isArray(target)
|
|
153
|
+
? target.find((keyframe) => keyframe !== null)
|
|
154
|
+
: target;
|
|
155
|
+
// A numeric target on a vector reads the first component (uniform scale)
|
|
156
|
+
return typeof sample === "string" ? Array.from(value).join(" ") : value[0];
|
|
157
|
+
}
|
|
158
|
+
function applyValues(values, subject) {
|
|
159
|
+
const bag = {};
|
|
160
|
+
const vectors = new Map();
|
|
161
|
+
for (const key in values) {
|
|
162
|
+
const target = resolveKey(subject, key);
|
|
163
|
+
const { path, index, degrees } = target;
|
|
164
|
+
const value = values[key];
|
|
165
|
+
if (index === undefined) {
|
|
166
|
+
const parsed = parseValue(value, getVector(subject, path));
|
|
167
|
+
Array.isArray(parsed) && setShadow(subject, path, parsed);
|
|
168
|
+
setPath(bag, path, parsed);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const id = path.join(".");
|
|
172
|
+
const entry = vectors.get(id) ?? {
|
|
173
|
+
...target,
|
|
174
|
+
vector: getVector(subject, path) ?? [],
|
|
175
|
+
};
|
|
176
|
+
vectors.set(id, entry);
|
|
177
|
+
entry.vector[index] = degrees
|
|
178
|
+
? value * radiansPerDegree
|
|
179
|
+
: value;
|
|
180
|
+
}
|
|
181
|
+
vectors.forEach(({ path, vector }) => {
|
|
182
|
+
setShadow(subject, path, vector);
|
|
183
|
+
setPath(bag, path, vector);
|
|
184
|
+
});
|
|
185
|
+
typeof subject.set === "function"
|
|
186
|
+
? subject.set(bag)
|
|
187
|
+
: Object.assign(subject, bag);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Converts animated strings into vgpu values: CSS colors become linear RGB
|
|
191
|
+
* (matching vgpu/scene's srgb()), "1 0 0" becomes [1, 0, 0].
|
|
192
|
+
*/
|
|
193
|
+
function parseValue(value, current) {
|
|
194
|
+
if (typeof value !== "string")
|
|
195
|
+
return value;
|
|
196
|
+
if (color.test(value)) {
|
|
197
|
+
const parsed = color.parse(value);
|
|
198
|
+
const { red, green, blue, alpha = 1, } = "hue" in parsed ? hslaToRgba(parsed) : parsed;
|
|
199
|
+
const linear = [red, green, blue].map((channel) => toLinear(channel / 255));
|
|
200
|
+
return current?.length === 4 ? [...linear, alpha] : linear;
|
|
201
|
+
}
|
|
202
|
+
const parts = value
|
|
203
|
+
.split(",")
|
|
204
|
+
.join(" ")
|
|
205
|
+
.split(" ")
|
|
206
|
+
.filter(Boolean)
|
|
207
|
+
.map(Number);
|
|
208
|
+
return parts.length > 1 && parts.every((part) => !isNaN(part))
|
|
209
|
+
? parts
|
|
210
|
+
: value;
|
|
211
|
+
}
|
|
212
|
+
function toLinear(channel) {
|
|
213
|
+
return channel <= 0.04045
|
|
214
|
+
? channel / 12.92
|
|
215
|
+
: ((channel + 0.055) / 1.055) ** 2.4;
|
|
216
|
+
}
|
|
217
|
+
function toSRGB(channel) {
|
|
218
|
+
return channel <= 0.0031308
|
|
219
|
+
? channel * 12.92
|
|
220
|
+
: 1.055 * channel ** (1 / 2.4) - 0.055;
|
|
221
|
+
}
|
|
222
|
+
function toColorString(linear) {
|
|
223
|
+
const [red, green, blue, alpha = 1] = Array.from(linear).map((channel, i) => i < 3 ? toSRGB(channel) * 255 : channel);
|
|
224
|
+
return rgba.transform({ red, green, blue, alpha });
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Inverse of vgpu's quatFromEuler (intrinsic XYZ).
|
|
228
|
+
*/
|
|
229
|
+
function eulerFromQuaternion(quaternion) {
|
|
230
|
+
const [x, y, z, w] = Array.from(quaternion);
|
|
231
|
+
const m13 = 2 * (x * z + w * y);
|
|
232
|
+
const singular = Math.abs(m13) > 0.9999999;
|
|
233
|
+
return [
|
|
234
|
+
singular
|
|
235
|
+
? Math.atan2(2 * (y * z + w * x), 1 - 2 * (x * x + z * z))
|
|
236
|
+
: Math.atan2(-2 * (y * z - w * x), 1 - 2 * (x * x + y * y)),
|
|
237
|
+
Math.asin(clamp(-1, 1, m13)),
|
|
238
|
+
singular
|
|
239
|
+
? 0
|
|
240
|
+
: Math.atan2(-2 * (x * y - w * z), 1 - 2 * (y * y + z * z)),
|
|
241
|
+
];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export { vgpuEffect };
|
package/dist/motion.dev.js
CHANGED
|
@@ -1039,15 +1039,16 @@
|
|
|
1039
1039
|
* to prevent infinite loops
|
|
1040
1040
|
*/
|
|
1041
1041
|
const maxGeneratorDuration = 20000;
|
|
1042
|
-
function calcGeneratorDuration(generator) {
|
|
1042
|
+
function calcGeneratorDuration(generator, timeStep = 50, maxDuration = maxGeneratorDuration, keyframes) {
|
|
1043
1043
|
let duration = 0;
|
|
1044
|
-
const timeStep = 50;
|
|
1045
1044
|
let state = generator.next(duration);
|
|
1046
|
-
|
|
1045
|
+
keyframes?.push(state.value);
|
|
1046
|
+
while (!state.done && duration < maxDuration) {
|
|
1047
1047
|
duration += timeStep;
|
|
1048
1048
|
state = generator.next(duration);
|
|
1049
|
+
keyframes?.push(state.value);
|
|
1049
1050
|
}
|
|
1050
|
-
return duration >=
|
|
1051
|
+
return duration >= maxDuration ? Infinity : duration;
|
|
1051
1052
|
}
|
|
1052
1053
|
|
|
1053
1054
|
/**
|
|
@@ -1131,9 +1132,13 @@
|
|
|
1131
1132
|
const exponentialDecay = undampedFreq * dampingRatio;
|
|
1132
1133
|
const delta = exponentialDecay * duration;
|
|
1133
1134
|
const d = delta * velocity + velocity;
|
|
1134
|
-
const e =
|
|
1135
|
+
const e = dampingRatio *
|
|
1136
|
+
dampingRatio *
|
|
1137
|
+
undampedFreq *
|
|
1138
|
+
undampedFreq *
|
|
1139
|
+
duration;
|
|
1135
1140
|
const f = Math.exp(-delta);
|
|
1136
|
-
const g = calcAngularFreq(
|
|
1141
|
+
const g = calcAngularFreq(undampedFreq * undampedFreq, dampingRatio);
|
|
1137
1142
|
const factor = -envelope(undampedFreq) + safeMin > 0 ? -1 : 1;
|
|
1138
1143
|
return (factor * ((d - e) * f)) / g;
|
|
1139
1144
|
};
|
|
@@ -1164,7 +1169,7 @@
|
|
|
1164
1169
|
};
|
|
1165
1170
|
}
|
|
1166
1171
|
else {
|
|
1167
|
-
const stiffness =
|
|
1172
|
+
const stiffness = undampedFreq * undampedFreq * mass;
|
|
1168
1173
|
return {
|
|
1169
1174
|
stiffness,
|
|
1170
1175
|
damping: dampingRatio * 2 * Math.sqrt(mass * stiffness),
|
|
@@ -1244,6 +1249,7 @@
|
|
|
1244
1249
|
const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass));
|
|
1245
1250
|
const initialDelta = target - origin;
|
|
1246
1251
|
const undampedAngularFreq = millisecondsToSeconds(Math.sqrt(stiffness / mass));
|
|
1252
|
+
const decay = dampingRatio * undampedAngularFreq;
|
|
1247
1253
|
/**
|
|
1248
1254
|
* If we're working on a granular scale, use smaller defaults for determining
|
|
1249
1255
|
* when the spring is finished.
|
|
@@ -1260,35 +1266,38 @@
|
|
|
1260
1266
|
: springDefaults.restDelta.default);
|
|
1261
1267
|
let resolveSpring;
|
|
1262
1268
|
let resolveVelocity;
|
|
1263
|
-
// Underdamped coefficients, hoisted for use in the inlined next() hot path
|
|
1264
|
-
let angularFreq;
|
|
1265
|
-
let A;
|
|
1266
|
-
let sinCoeff;
|
|
1267
|
-
let cosCoeff;
|
|
1268
1269
|
if (dampingRatio < 1) {
|
|
1269
|
-
angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
|
|
1270
|
-
A =
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1270
|
+
const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
|
|
1271
|
+
const A = (initialVelocity + decay * initialDelta) / angularFreq;
|
|
1272
|
+
// Coefficients for the analytical derivative (px/ms)
|
|
1273
|
+
const sinCoeff = decay * A + initialDelta * angularFreq;
|
|
1274
|
+
const cosCoeff = decay * initialDelta - A * angularFreq;
|
|
1275
|
+
/**
|
|
1276
|
+
* The underdamped hot path needs both position and velocity every
|
|
1277
|
+
* frame and they share the same exp/sin/cos terms, so sample both
|
|
1278
|
+
* at once, memoized by t, to only calculate them once per frame.
|
|
1279
|
+
*/
|
|
1280
|
+
let sampledT = -1;
|
|
1281
|
+
let position = 0;
|
|
1282
|
+
let velocityAtT = 0;
|
|
1283
|
+
const sample = (t) => {
|
|
1284
|
+
if (t !== sampledT) {
|
|
1285
|
+
sampledT = t;
|
|
1286
|
+
const envelope = Math.exp(-decay * t);
|
|
1287
|
+
const sin = Math.sin(angularFreq * t);
|
|
1288
|
+
const cos = Math.cos(angularFreq * t);
|
|
1289
|
+
position = target - envelope * (A * sin + initialDelta * cos);
|
|
1290
|
+
velocityAtT = envelope * (sinCoeff * sin + cosCoeff * cos);
|
|
1291
|
+
}
|
|
1292
|
+
};
|
|
1274
1293
|
// Underdamped spring
|
|
1275
1294
|
resolveSpring = (t) => {
|
|
1276
|
-
|
|
1277
|
-
return
|
|
1278
|
-
envelope *
|
|
1279
|
-
(A * Math.sin(angularFreq * t) +
|
|
1280
|
-
initialDelta * Math.cos(angularFreq * t)));
|
|
1295
|
+
sample(t);
|
|
1296
|
+
return position;
|
|
1281
1297
|
};
|
|
1282
|
-
// Analytical derivative of underdamped spring (px/ms)
|
|
1283
|
-
sinCoeff =
|
|
1284
|
-
dampingRatio * undampedAngularFreq * A + initialDelta * angularFreq;
|
|
1285
|
-
cosCoeff =
|
|
1286
|
-
dampingRatio * undampedAngularFreq * initialDelta - A * angularFreq;
|
|
1287
1298
|
resolveVelocity = (t) => {
|
|
1288
|
-
|
|
1289
|
-
return
|
|
1290
|
-
(sinCoeff * Math.sin(angularFreq * t) +
|
|
1291
|
-
cosCoeff * Math.cos(angularFreq * t));
|
|
1299
|
+
sample(t);
|
|
1300
|
+
return velocityAtT;
|
|
1292
1301
|
};
|
|
1293
1302
|
}
|
|
1294
1303
|
else if (dampingRatio === 1) {
|
|
@@ -1306,13 +1315,12 @@
|
|
|
1306
1315
|
// Overdamped spring
|
|
1307
1316
|
const dampedAngularFreq = undampedAngularFreq * Math.sqrt(dampingRatio * dampingRatio - 1);
|
|
1308
1317
|
resolveSpring = (t) => {
|
|
1309
|
-
const envelope = Math.exp(-
|
|
1318
|
+
const envelope = Math.exp(-decay * t);
|
|
1310
1319
|
// When performing sinh or cosh values can hit Infinity so we cap them here
|
|
1311
1320
|
const freqForT = Math.min(dampedAngularFreq * t, 300);
|
|
1312
1321
|
return (target -
|
|
1313
1322
|
(envelope *
|
|
1314
|
-
((initialVelocity +
|
|
1315
|
-
dampingRatio * undampedAngularFreq * initialDelta) *
|
|
1323
|
+
((initialVelocity + decay * initialDelta) *
|
|
1316
1324
|
Math.sinh(freqForT) +
|
|
1317
1325
|
dampedAngularFreq *
|
|
1318
1326
|
initialDelta *
|
|
@@ -1320,43 +1328,21 @@
|
|
|
1320
1328
|
dampedAngularFreq);
|
|
1321
1329
|
};
|
|
1322
1330
|
// Analytical derivative of overdamped spring (px/ms)
|
|
1323
|
-
const P = (initialVelocity +
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
const sinhCoeff = dampingRatio * undampedAngularFreq * P - initialDelta * dampedAngularFreq;
|
|
1327
|
-
const coshCoeff = dampingRatio * undampedAngularFreq * initialDelta - P * dampedAngularFreq;
|
|
1331
|
+
const P = (initialVelocity + decay * initialDelta) / dampedAngularFreq;
|
|
1332
|
+
const sinhCoeff = decay * P - initialDelta * dampedAngularFreq;
|
|
1333
|
+
const coshCoeff = decay * initialDelta - P * dampedAngularFreq;
|
|
1328
1334
|
resolveVelocity = (t) => {
|
|
1329
|
-
const envelope = Math.exp(-
|
|
1335
|
+
const envelope = Math.exp(-decay * t);
|
|
1330
1336
|
const freqForT = Math.min(dampedAngularFreq * t, 300);
|
|
1331
|
-
return envelope *
|
|
1337
|
+
return (envelope *
|
|
1332
1338
|
(sinhCoeff * Math.sinh(freqForT) +
|
|
1333
|
-
coshCoeff * Math.cosh(freqForT));
|
|
1339
|
+
coshCoeff * Math.cosh(freqForT)));
|
|
1334
1340
|
};
|
|
1335
1341
|
}
|
|
1336
1342
|
const generator = {
|
|
1337
1343
|
calculatedDuration: isResolvedFromDuration ? duration || null : null,
|
|
1338
1344
|
velocity: (t) => secondsToMilliseconds(resolveVelocity(t)),
|
|
1339
1345
|
next: (t) => {
|
|
1340
|
-
/**
|
|
1341
|
-
* For underdamped physics springs we need both position and
|
|
1342
|
-
* velocity each tick. Compute shared trig values once to avoid
|
|
1343
|
-
* duplicate Math.exp/sin/cos calls on the hot path.
|
|
1344
|
-
*/
|
|
1345
|
-
if (!isResolvedFromDuration && dampingRatio < 1) {
|
|
1346
|
-
const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
|
|
1347
|
-
const sin = Math.sin(angularFreq * t);
|
|
1348
|
-
const cos = Math.cos(angularFreq * t);
|
|
1349
|
-
const current = target -
|
|
1350
|
-
envelope *
|
|
1351
|
-
(A * sin + initialDelta * cos);
|
|
1352
|
-
const currentVelocity = secondsToMilliseconds(envelope *
|
|
1353
|
-
(sinCoeff * sin + cosCoeff * cos));
|
|
1354
|
-
state.done =
|
|
1355
|
-
Math.abs(currentVelocity) <= restSpeed &&
|
|
1356
|
-
Math.abs(target - current) <= restDelta;
|
|
1357
|
-
state.value = state.done ? target : current;
|
|
1358
|
-
return state;
|
|
1359
|
-
}
|
|
1360
1346
|
const current = resolveSpring(t);
|
|
1361
1347
|
if (!isResolvedFromDuration) {
|
|
1362
1348
|
const currentVelocity = secondsToMilliseconds(resolveVelocity(t));
|
|
@@ -1387,19 +1373,13 @@
|
|
|
1387
1373
|
return options;
|
|
1388
1374
|
};
|
|
1389
1375
|
|
|
1390
|
-
const velocitySampleDuration = 5; // ms
|
|
1391
|
-
function getGeneratorVelocity(resolveValue, t, current) {
|
|
1392
|
-
const prevT = Math.max(t - velocitySampleDuration, 0);
|
|
1393
|
-
return velocityPerSecond(current - resolveValue(prevT), t - prevT);
|
|
1394
|
-
}
|
|
1395
|
-
|
|
1396
1376
|
function inertia({ keyframes, velocity = 0.0, power = 0.8, timeConstant = 325, bounceDamping = 10, bounceStiffness = 500, modifyTarget, min, max, restDelta = 0.5, restSpeed, }) {
|
|
1397
1377
|
const origin = keyframes[0];
|
|
1398
1378
|
const state = {
|
|
1399
1379
|
done: false,
|
|
1400
1380
|
value: origin,
|
|
1401
1381
|
};
|
|
1402
|
-
const isOutOfBounds = (v) =>
|
|
1382
|
+
const isOutOfBounds = (v) => v < min || v > max;
|
|
1403
1383
|
const nearestBoundary = (v) => {
|
|
1404
1384
|
if (min === undefined)
|
|
1405
1385
|
return max;
|
|
@@ -1417,12 +1397,10 @@
|
|
|
1417
1397
|
if (target !== ideal)
|
|
1418
1398
|
amplitude = target - origin;
|
|
1419
1399
|
const calcDelta = (t) => -amplitude * Math.exp(-t / timeConstant);
|
|
1420
|
-
const calcLatest = (t) => target + calcDelta(t);
|
|
1421
1400
|
const applyFriction = (t) => {
|
|
1422
1401
|
const delta = calcDelta(t);
|
|
1423
|
-
const latest = calcLatest(t);
|
|
1424
1402
|
state.done = Math.abs(delta) <= restDelta;
|
|
1425
|
-
state.value = state.done ? target :
|
|
1403
|
+
state.value = state.done ? target : target + delta;
|
|
1426
1404
|
};
|
|
1427
1405
|
/**
|
|
1428
1406
|
* Ideally this would resolve for t in a stateless way, we could
|
|
@@ -1438,7 +1416,12 @@
|
|
|
1438
1416
|
timeReachedBoundary = t;
|
|
1439
1417
|
spring$1 = spring({
|
|
1440
1418
|
keyframes: [state.value, nearestBoundary(state.value)],
|
|
1441
|
-
|
|
1419
|
+
/**
|
|
1420
|
+
* The friction curve is target + calcDelta(t), so its exact
|
|
1421
|
+
* derivative is -calcDelta(t) / timeConstant in units/ms,
|
|
1422
|
+
* converted here to the units/second expected by spring.
|
|
1423
|
+
*/
|
|
1424
|
+
velocity: (-calcDelta(t) / timeConstant) * 1000,
|
|
1442
1425
|
damping: bounceDamping,
|
|
1443
1426
|
stiffness: bounceStiffness,
|
|
1444
1427
|
restDelta,
|
|
@@ -1607,6 +1590,12 @@
|
|
|
1607
1590
|
};
|
|
1608
1591
|
}
|
|
1609
1592
|
|
|
1593
|
+
const velocitySampleDuration = 5; // ms
|
|
1594
|
+
function getGeneratorVelocity(resolveValue, t, current) {
|
|
1595
|
+
const prevT = Math.max(t - velocitySampleDuration, 0);
|
|
1596
|
+
return velocityPerSecond(current - resolveValue(prevT), t - prevT);
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1610
1599
|
const isNotNull = (value) => value !== null;
|
|
1611
1600
|
function getFinalKeyframe(keyframes, { repeat, repeatType = "loop" }, finalKeyframe, speed = 1) {
|
|
1612
1601
|
const resolvedKeyframes = keyframes.filter(isNotNull);
|
|
@@ -4612,6 +4601,47 @@
|
|
|
4612
4601
|
return true;
|
|
4613
4602
|
});
|
|
4614
4603
|
|
|
4604
|
+
/**
|
|
4605
|
+
* Effects registered via `animate.addEffect()`, most recent first.
|
|
4606
|
+
*/
|
|
4607
|
+
const effects = [];
|
|
4608
|
+
function addEffect(effect) {
|
|
4609
|
+
exports.invariant(typeof effect.test === "function" && typeof effect.read === "function", "Effects passed to animate.addEffect() need test() and read().", "effect-missing-test");
|
|
4610
|
+
removeEffect(effect);
|
|
4611
|
+
effects.unshift(effect);
|
|
4612
|
+
}
|
|
4613
|
+
function removeEffect(effect) {
|
|
4614
|
+
removeItem(effects, effect);
|
|
4615
|
+
}
|
|
4616
|
+
function findEffect(subject) {
|
|
4617
|
+
return effects.find((effect) => effect.test(subject));
|
|
4618
|
+
}
|
|
4619
|
+
/**
|
|
4620
|
+
* Animate the keys of `subject` via `effect`. Motion values are created on
|
|
4621
|
+
* first animation, seeded from `effect.read()` or the first keyframe, and
|
|
4622
|
+
* then bound to the subject via the effect for the rest of its life.
|
|
4623
|
+
*/
|
|
4624
|
+
function animateEffectSubject(effect, subject, keyframes, transition = {}) {
|
|
4625
|
+
const animations = [];
|
|
4626
|
+
for (const key in keyframes) {
|
|
4627
|
+
const target = keyframes[key];
|
|
4628
|
+
let value = effect.get(subject, key);
|
|
4629
|
+
if (!value) {
|
|
4630
|
+
const initial = effect.read(subject, key, target) ?? firstKeyframe(target);
|
|
4631
|
+
exports.invariant(initial !== undefined, `"${key}" can't be read from the animated subject. Provide [from, to] keyframes.`, "effect-unreadable-value");
|
|
4632
|
+
value = motionValue(initial);
|
|
4633
|
+
effect(subject, { [key]: value });
|
|
4634
|
+
}
|
|
4635
|
+
value.start(animateMotionValue(key, value, target, getValueTransition$1(transition, key)));
|
|
4636
|
+
value.animation && animations.push(value.animation);
|
|
4637
|
+
}
|
|
4638
|
+
return animations;
|
|
4639
|
+
}
|
|
4640
|
+
function firstKeyframe(target) {
|
|
4641
|
+
const first = Array.isArray(target) ? target[0] : undefined;
|
|
4642
|
+
return first === null ? undefined : first;
|
|
4643
|
+
}
|
|
4644
|
+
|
|
4615
4645
|
function resolveElements(elementOrSelector, scope, selectorCache) {
|
|
4616
4646
|
if (elementOrSelector == null) {
|
|
4617
4647
|
return [];
|
|
@@ -4656,7 +4686,13 @@
|
|
|
4656
4686
|
};
|
|
4657
4687
|
|
|
4658
4688
|
class MotionValueState {
|
|
4659
|
-
|
|
4689
|
+
/**
|
|
4690
|
+
* @param step - The frameloop step renders are scheduled in. Defaults
|
|
4691
|
+
* to `frame.render`. Effects that feed a render loop running in
|
|
4692
|
+
* `frame.render` (GPU scenes) should write in `frame.preRender`.
|
|
4693
|
+
*/
|
|
4694
|
+
constructor(step = frame.render) {
|
|
4695
|
+
this.step = step;
|
|
4660
4696
|
this.latest = {};
|
|
4661
4697
|
this.values = new Map();
|
|
4662
4698
|
}
|
|
@@ -4673,7 +4709,7 @@
|
|
|
4673
4709
|
else {
|
|
4674
4710
|
this.latest[name] = v;
|
|
4675
4711
|
}
|
|
4676
|
-
render &&
|
|
4712
|
+
render && this.step(render);
|
|
4677
4713
|
};
|
|
4678
4714
|
onChange();
|
|
4679
4715
|
const cancelOnChange = value.on("change", onChange);
|
|
@@ -4692,10 +4728,10 @@
|
|
|
4692
4728
|
}
|
|
4693
4729
|
}
|
|
4694
4730
|
|
|
4695
|
-
function createEffect(addValue) {
|
|
4731
|
+
function createEffect(addValue, { step, ...options } = {}) {
|
|
4696
4732
|
const stateCache = new WeakMap();
|
|
4697
|
-
|
|
4698
|
-
const state = stateCache.get(subject) ?? new MotionValueState();
|
|
4733
|
+
const effect = (subject, values) => {
|
|
4734
|
+
const state = stateCache.get(subject) ?? new MotionValueState(step);
|
|
4699
4735
|
stateCache.set(subject, state);
|
|
4700
4736
|
const subscriptions = [];
|
|
4701
4737
|
for (const key in values) {
|
|
@@ -4708,6 +4744,9 @@
|
|
|
4708
4744
|
cancel();
|
|
4709
4745
|
};
|
|
4710
4746
|
};
|
|
4747
|
+
return Object.assign(effect, options, {
|
|
4748
|
+
get: (subject, key) => stateCache.get(subject)?.get(key),
|
|
4749
|
+
});
|
|
4711
4750
|
}
|
|
4712
4751
|
|
|
4713
4752
|
function canSetAsProperty(element, name) {
|
|
@@ -11533,13 +11572,6 @@
|
|
|
11533
11572
|
exports.invariant(Boolean(numSubjects), "No valid elements provided.", "no-valid-elements");
|
|
11534
11573
|
for (let i = 0; i < numSubjects; i++) {
|
|
11535
11574
|
const thisSubject = subjects[i];
|
|
11536
|
-
const createVisualElement = thisSubject instanceof Element
|
|
11537
|
-
? createDOMVisualElement
|
|
11538
|
-
: createObjectVisualElement;
|
|
11539
|
-
if (!visualElementStore.has(thisSubject)) {
|
|
11540
|
-
createVisualElement(thisSubject);
|
|
11541
|
-
}
|
|
11542
|
-
const visualElement = visualElementStore.get(thisSubject);
|
|
11543
11575
|
const transition = { ...options };
|
|
11544
11576
|
/**
|
|
11545
11577
|
* Resolve stagger function if provided.
|
|
@@ -11548,6 +11580,23 @@
|
|
|
11548
11580
|
typeof transition.delay === "function") {
|
|
11549
11581
|
transition.delay = transition.delay(i, numSubjects);
|
|
11550
11582
|
}
|
|
11583
|
+
const isElement = thisSubject instanceof Element;
|
|
11584
|
+
/**
|
|
11585
|
+
* Registered effects (animate.addEffect) claim non-DOM subjects
|
|
11586
|
+
* before we fall back to treating them as plain objects.
|
|
11587
|
+
*/
|
|
11588
|
+
const effect = isElement ? undefined : findEffect(thisSubject);
|
|
11589
|
+
if (effect) {
|
|
11590
|
+
animations.push(...animateEffectSubject(effect, thisSubject, keyframes, transition));
|
|
11591
|
+
continue;
|
|
11592
|
+
}
|
|
11593
|
+
const createVisualElement = isElement
|
|
11594
|
+
? createDOMVisualElement
|
|
11595
|
+
: createObjectVisualElement;
|
|
11596
|
+
if (!visualElementStore.has(thisSubject)) {
|
|
11597
|
+
createVisualElement(thisSubject);
|
|
11598
|
+
}
|
|
11599
|
+
const visualElement = visualElementStore.get(thisSubject);
|
|
11551
11600
|
animations.push(...animateTarget(visualElement, { ...keyframes, transition }, {}));
|
|
11552
11601
|
}
|
|
11553
11602
|
}
|
|
@@ -11633,7 +11682,16 @@
|
|
|
11633
11682
|
}
|
|
11634
11683
|
return scopedAnimate;
|
|
11635
11684
|
}
|
|
11636
|
-
const animate = createScopedAnimate()
|
|
11685
|
+
const animate = Object.assign(createScopedAnimate(), {
|
|
11686
|
+
/**
|
|
11687
|
+
* Register an effect so `animate()` can animate the subjects it
|
|
11688
|
+
* claims, for instance `animate.addEffect(threeEffect)`. The most
|
|
11689
|
+
* recently added effect is tested first. DOM elements are always
|
|
11690
|
+
* animated directly.
|
|
11691
|
+
*/
|
|
11692
|
+
addEffect,
|
|
11693
|
+
removeEffect,
|
|
11694
|
+
});
|
|
11637
11695
|
|
|
11638
11696
|
function animateElements(elementOrSelector, keyframes, options, scope) {
|
|
11639
11697
|
// Gracefully handle null/undefined elements (e.g., from querySelector returning null)
|
|
@@ -12408,6 +12466,7 @@
|
|
|
12408
12466
|
exports.LayoutAnimationBuilder = LayoutAnimationBuilder;
|
|
12409
12467
|
exports.MotionGlobalConfig = MotionGlobalConfig;
|
|
12410
12468
|
exports.MotionValue = MotionValue;
|
|
12469
|
+
exports.MotionValueState = MotionValueState;
|
|
12411
12470
|
exports.NativeAnimation = NativeAnimation;
|
|
12412
12471
|
exports.NativeAnimationExtended = NativeAnimationExtended;
|
|
12413
12472
|
exports.NativeAnimationWrapper = NativeAnimationWrapper;
|
|
@@ -12420,6 +12479,7 @@
|
|
|
12420
12479
|
exports.acceleratedValues = acceleratedValues;
|
|
12421
12480
|
exports.addAttrValue = addAttrValue;
|
|
12422
12481
|
exports.addDomEvent = addDomEvent;
|
|
12482
|
+
exports.addEffect = addEffect;
|
|
12423
12483
|
exports.addScaleCorrector = addScaleCorrector;
|
|
12424
12484
|
exports.addStyleValue = addStyleValue;
|
|
12425
12485
|
exports.addUniqueItem = addUniqueItem;
|
|
@@ -12427,6 +12487,7 @@
|
|
|
12427
12487
|
exports.alpha = alpha;
|
|
12428
12488
|
exports.analyseComplexValue = analyseComplexValue;
|
|
12429
12489
|
exports.animate = animate;
|
|
12490
|
+
exports.animateEffectSubject = animateEffectSubject;
|
|
12430
12491
|
exports.animateMini = animateMini;
|
|
12431
12492
|
exports.animateMotionValue = animateMotionValue;
|
|
12432
12493
|
exports.animateSingleValue = animateSingleValue;
|
|
@@ -12499,6 +12560,7 @@
|
|
|
12499
12560
|
exports.createAxisDelta = createAxisDelta;
|
|
12500
12561
|
exports.createBox = createBox;
|
|
12501
12562
|
exports.createDelta = createDelta;
|
|
12563
|
+
exports.createEffect = createEffect;
|
|
12502
12564
|
exports.createGeneratorEasing = createGeneratorEasing;
|
|
12503
12565
|
exports.createProjectionNode = createProjectionNode;
|
|
12504
12566
|
exports.createRenderBatcher = createRenderBatcher;
|
|
@@ -12523,6 +12585,7 @@
|
|
|
12523
12585
|
exports.fillOffset = fillOffset;
|
|
12524
12586
|
exports.fillWildcards = fillWildcards;
|
|
12525
12587
|
exports.findDimensionValueType = findDimensionValueType;
|
|
12588
|
+
exports.findEffect = findEffect;
|
|
12526
12589
|
exports.findValueType = findValueType;
|
|
12527
12590
|
exports.flushKeyframeResolvers = flushKeyframeResolvers;
|
|
12528
12591
|
exports.followValue = followValue;
|
|
@@ -12641,6 +12704,7 @@
|
|
|
12641
12704
|
exports.removeAxisDelta = removeAxisDelta;
|
|
12642
12705
|
exports.removeAxisTransforms = removeAxisTransforms;
|
|
12643
12706
|
exports.removeBoxTransforms = removeBoxTransforms;
|
|
12707
|
+
exports.removeEffect = removeEffect;
|
|
12644
12708
|
exports.removeItem = removeItem;
|
|
12645
12709
|
exports.removePointDelta = removePointDelta;
|
|
12646
12710
|
exports.renderHTML = renderHTML;
|