framer-motion 7.8.0 → 7.9.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 +6 -5
- package/dist/cjs/index.js +2030 -1920
- package/dist/es/animation/animate.mjs +2 -2
- package/dist/es/animation/create-instant-animation.mjs +12 -0
- package/dist/es/animation/{animation-controls.mjs → hooks/animation-controls.mjs} +2 -2
- package/dist/es/animation/{use-animated-state.mjs → hooks/use-animated-state.mjs} +6 -6
- package/dist/es/animation/{use-animation.mjs → hooks/use-animation.mjs} +1 -1
- package/dist/es/animation/index.mjs +124 -0
- package/dist/es/animation/legacy-popmotion/decay.mjs +11 -4
- package/dist/es/animation/legacy-popmotion/index.mjs +22 -11
- package/dist/es/animation/legacy-popmotion/inertia.mjs +14 -8
- package/dist/es/animation/legacy-popmotion/keyframes.mjs +21 -13
- package/dist/es/animation/legacy-popmotion/spring.mjs +13 -11
- package/dist/es/animation/utils/default-transitions.mjs +9 -14
- package/dist/es/animation/utils/keyframes.mjs +41 -0
- package/dist/es/animation/utils/transitions.mjs +1 -166
- package/dist/es/animation/waapi/create-accelerated-animation.mjs +82 -0
- package/dist/es/animation/waapi/index.mjs +4 -6
- package/dist/es/gestures/drag/VisualElementDragControls.mjs +2 -2
- package/dist/es/index.mjs +3 -3
- package/dist/es/render/VisualElement.mjs +1 -1
- package/dist/es/render/utils/animation.mjs +2 -2
- package/dist/es/render/utils/motion-values.mjs +2 -2
- package/dist/es/render/utils/setters.mjs +1 -1
- package/dist/es/value/index.mjs +11 -5
- package/dist/es/value/use-spring.mjs +1 -2
- package/dist/framer-motion.dev.js +2051 -1941
- package/dist/framer-motion.js +1 -1
- package/dist/index.d.ts +409 -348
- package/dist/projection.dev.js +1672 -1535
- package/dist/size-rollup-dom-animation-assets.js +1 -1
- package/dist/size-rollup-dom-animation.js +1 -1
- package/dist/size-rollup-dom-max-assets.js +1 -1
- package/dist/size-rollup-dom-max.js +1 -1
- package/dist/size-rollup-motion.js +1 -1
- package/dist/size-webpack-dom-animation.js +1 -1
- package/dist/size-webpack-dom-max.js +1 -1
- package/dist/three-entry.d.ts +287 -281
- package/package.json +8 -8
package/dist/projection.dev.js
CHANGED
|
@@ -161,386 +161,514 @@
|
|
|
161
161
|
onNextFrame(processFrame);
|
|
162
162
|
};
|
|
163
163
|
|
|
164
|
-
function
|
|
165
|
-
|
|
166
|
-
|
|
164
|
+
var warning = function () { };
|
|
165
|
+
var invariant = function () { };
|
|
166
|
+
{
|
|
167
|
+
warning = function (check, message) {
|
|
168
|
+
if (!check && typeof console !== 'undefined') {
|
|
169
|
+
console.warn(message);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
invariant = function (check, message) {
|
|
173
|
+
if (!check) {
|
|
174
|
+
throw new Error(message);
|
|
175
|
+
}
|
|
176
|
+
};
|
|
167
177
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Converts seconds to milliseconds
|
|
181
|
+
*
|
|
182
|
+
* @param seconds - Time in seconds.
|
|
183
|
+
* @return milliseconds - Converted time in milliseconds.
|
|
184
|
+
*/
|
|
185
|
+
const secondsToMilliseconds = (seconds) => seconds * 1000;
|
|
186
|
+
|
|
187
|
+
const instantAnimationState = {
|
|
188
|
+
current: false,
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// Accepts an easing function and returns a new one that outputs mirrored values for
|
|
192
|
+
// the second half of the animation. Turns easeIn into easeInOut.
|
|
193
|
+
const mirrorEasing = (easing) => (p) => p <= 0.5 ? easing(2 * p) / 2 : (2 - easing(2 * (1 - p))) / 2;
|
|
194
|
+
|
|
195
|
+
// Accepts an easing function and returns a new one that outputs reversed values.
|
|
196
|
+
// Turns easeIn into easeOut.
|
|
197
|
+
const reverseEasing = (easing) => (p) => 1 - easing(1 - p);
|
|
198
|
+
|
|
199
|
+
const easeIn = (p) => p * p;
|
|
200
|
+
const easeOut = reverseEasing(easeIn);
|
|
201
|
+
const easeInOut = mirrorEasing(easeIn);
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* TODO: When we move from string as a source of truth to data models
|
|
205
|
+
* everything in this folder should probably be referred to as models vs types
|
|
206
|
+
*/
|
|
207
|
+
// If this number is a decimal, make it just five decimal places
|
|
208
|
+
// to avoid exponents
|
|
209
|
+
const sanitize = (v) => Math.round(v * 100000) / 100000;
|
|
210
|
+
const floatRegex = /(-)?([\d]*\.?[\d])+/g;
|
|
211
|
+
const colorRegex = /(#[0-9a-f]{6}|#[0-9a-f]{3}|#(?:[0-9a-f]{2}){2,4}|(rgb|hsl)a?\((-?[\d\.]+%?[,\s]+){2}(-?[\d\.]+%?)\s*[\,\/]?\s*[\d\.]*%?\))/gi;
|
|
212
|
+
const singleColorRegex = /^(#[0-9a-f]{3}|#(?:[0-9a-f]{2}){2,4}|(rgb|hsl)a?\((-?[\d\.]+%?[,\s]+){2}(-?[\d\.]+%?)\s*[\,\/]?\s*[\d\.]*%?\))$/i;
|
|
213
|
+
function isString(v) {
|
|
214
|
+
return typeof v === "string";
|
|
172
215
|
}
|
|
173
216
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
217
|
+
const clamp = (min, max, v) => Math.min(Math.max(v, min), max);
|
|
218
|
+
|
|
219
|
+
const number = {
|
|
220
|
+
test: (v) => typeof v === "number",
|
|
221
|
+
parse: parseFloat,
|
|
222
|
+
transform: (v) => v,
|
|
223
|
+
};
|
|
224
|
+
const alpha = {
|
|
225
|
+
...number,
|
|
226
|
+
transform: (v) => clamp(0, 1, v),
|
|
227
|
+
};
|
|
228
|
+
const scale = {
|
|
229
|
+
...number,
|
|
230
|
+
default: 1,
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Returns true if the provided string is a color, ie rgba(0,0,0,0) or #000,
|
|
235
|
+
* but false if a number or multiple colors
|
|
236
|
+
*/
|
|
237
|
+
const isColorString = (type, testProp) => (v) => {
|
|
238
|
+
return Boolean((isString(v) && singleColorRegex.test(v) && v.startsWith(type)) ||
|
|
239
|
+
(testProp && Object.prototype.hasOwnProperty.call(v, testProp)));
|
|
240
|
+
};
|
|
241
|
+
const splitColor = (aName, bName, cName) => (v) => {
|
|
242
|
+
if (!isString(v))
|
|
243
|
+
return v;
|
|
244
|
+
const [a, b, c, alpha] = v.match(floatRegex);
|
|
245
|
+
return {
|
|
246
|
+
[aName]: parseFloat(a),
|
|
247
|
+
[bName]: parseFloat(b),
|
|
248
|
+
[cName]: parseFloat(c),
|
|
249
|
+
alpha: alpha !== undefined ? parseFloat(alpha) : 1,
|
|
250
|
+
};
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const clampRgbUnit = (v) => clamp(0, 255, v);
|
|
254
|
+
const rgbUnit = {
|
|
255
|
+
...number,
|
|
256
|
+
transform: (v) => Math.round(clampRgbUnit(v)),
|
|
257
|
+
};
|
|
258
|
+
const rgba = {
|
|
259
|
+
test: isColorString("rgb", "red"),
|
|
260
|
+
parse: splitColor("red", "green", "blue"),
|
|
261
|
+
transform: ({ red, green, blue, alpha: alpha$1 = 1 }) => "rgba(" +
|
|
262
|
+
rgbUnit.transform(red) +
|
|
263
|
+
", " +
|
|
264
|
+
rgbUnit.transform(green) +
|
|
265
|
+
", " +
|
|
266
|
+
rgbUnit.transform(blue) +
|
|
267
|
+
", " +
|
|
268
|
+
sanitize(alpha.transform(alpha$1)) +
|
|
269
|
+
")",
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
function parseHex(v) {
|
|
273
|
+
let r = "";
|
|
274
|
+
let g = "";
|
|
275
|
+
let b = "";
|
|
276
|
+
let a = "";
|
|
277
|
+
// If we have 6 characters, ie #FF0000
|
|
278
|
+
if (v.length > 5) {
|
|
279
|
+
r = v.substring(1, 3);
|
|
280
|
+
g = v.substring(3, 5);
|
|
281
|
+
b = v.substring(5, 7);
|
|
282
|
+
a = v.substring(7, 9);
|
|
283
|
+
// Or we have 3 characters, ie #F00
|
|
177
284
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
285
|
+
else {
|
|
286
|
+
r = v.substring(1, 2);
|
|
287
|
+
g = v.substring(2, 3);
|
|
288
|
+
b = v.substring(3, 4);
|
|
289
|
+
a = v.substring(4, 5);
|
|
290
|
+
r += r;
|
|
291
|
+
g += g;
|
|
292
|
+
b += b;
|
|
293
|
+
a += a;
|
|
181
294
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
295
|
+
return {
|
|
296
|
+
red: parseInt(r, 16),
|
|
297
|
+
green: parseInt(g, 16),
|
|
298
|
+
blue: parseInt(b, 16),
|
|
299
|
+
alpha: a ? parseInt(a, 16) / 255 : 1,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
const hex = {
|
|
303
|
+
test: isColorString("#"),
|
|
304
|
+
parse: parseHex,
|
|
305
|
+
transform: rgba.transform,
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const createUnitType = (unit) => ({
|
|
309
|
+
test: (v) => isString(v) && v.endsWith(unit) && v.split(" ").length === 1,
|
|
310
|
+
parse: parseFloat,
|
|
311
|
+
transform: (v) => `${v}${unit}`,
|
|
312
|
+
});
|
|
313
|
+
const degrees = createUnitType("deg");
|
|
314
|
+
const percent = createUnitType("%");
|
|
315
|
+
const px = createUnitType("px");
|
|
316
|
+
const vh = createUnitType("vh");
|
|
317
|
+
const vw = createUnitType("vw");
|
|
318
|
+
const progressPercentage = {
|
|
319
|
+
...percent,
|
|
320
|
+
parse: (v) => percent.parse(v) / 100,
|
|
321
|
+
transform: (v) => percent.transform(v * 100),
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
const hsla = {
|
|
325
|
+
test: isColorString("hsl", "hue"),
|
|
326
|
+
parse: splitColor("hue", "saturation", "lightness"),
|
|
327
|
+
transform: ({ hue, saturation, lightness, alpha: alpha$1 = 1 }) => {
|
|
328
|
+
return ("hsla(" +
|
|
329
|
+
Math.round(hue) +
|
|
330
|
+
", " +
|
|
331
|
+
percent.transform(sanitize(saturation)) +
|
|
332
|
+
", " +
|
|
333
|
+
percent.transform(sanitize(lightness)) +
|
|
334
|
+
", " +
|
|
335
|
+
sanitize(alpha.transform(alpha$1)) +
|
|
336
|
+
")");
|
|
337
|
+
},
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
const color = {
|
|
341
|
+
test: (v) => rgba.test(v) || hex.test(v) || hsla.test(v),
|
|
342
|
+
parse: (v) => {
|
|
343
|
+
if (rgba.test(v)) {
|
|
344
|
+
return rgba.parse(v);
|
|
345
|
+
}
|
|
346
|
+
else if (hsla.test(v)) {
|
|
347
|
+
return hsla.parse(v);
|
|
191
348
|
}
|
|
192
349
|
else {
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Check whether the handler exists before firing as it's possible
|
|
196
|
-
* the subscriptions were modified during this loop running.
|
|
197
|
-
*/
|
|
198
|
-
const handler = this.subscriptions[i];
|
|
199
|
-
handler && handler(a, b, c);
|
|
200
|
-
}
|
|
350
|
+
return hex.parse(v);
|
|
201
351
|
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
return
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
352
|
+
},
|
|
353
|
+
transform: (v) => {
|
|
354
|
+
return isString(v)
|
|
355
|
+
? v
|
|
356
|
+
: v.hasOwnProperty("red")
|
|
357
|
+
? rgba.transform(v)
|
|
358
|
+
: hsla.transform(v);
|
|
359
|
+
},
|
|
360
|
+
};
|
|
210
361
|
|
|
211
362
|
/*
|
|
212
|
-
|
|
363
|
+
Value in range from progress
|
|
213
364
|
|
|
214
|
-
|
|
215
|
-
|
|
365
|
+
Given a lower limit and an upper limit, we return the value within
|
|
366
|
+
that range as expressed by progress (usually a number from 0 to 1)
|
|
367
|
+
|
|
368
|
+
So progress = 0.5 would change
|
|
369
|
+
|
|
370
|
+
from -------- to
|
|
371
|
+
|
|
372
|
+
to
|
|
373
|
+
|
|
374
|
+
from ---- to
|
|
375
|
+
|
|
376
|
+
E.g. from = 10, to = 20, progress = 0.5 => 15
|
|
377
|
+
|
|
378
|
+
@param [number]: Lower limit of range
|
|
379
|
+
@param [number]: Upper limit of range
|
|
380
|
+
@param [number]: The progress between lower and upper limits expressed 0-1
|
|
381
|
+
@return [number]: Value as calculated from progress within range (not limited within range)
|
|
216
382
|
*/
|
|
217
|
-
|
|
218
|
-
return frameDuration ? velocity * (1000 / frameDuration) : 0;
|
|
219
|
-
}
|
|
383
|
+
const mix = (from, to, progress) => -progress * from + progress * to + from;
|
|
220
384
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Duration, in milliseconds, since last updating frame.
|
|
246
|
-
*
|
|
247
|
-
* @internal
|
|
248
|
-
*/
|
|
249
|
-
this.timeDelta = 0;
|
|
250
|
-
/**
|
|
251
|
-
* Timestamp of the last time this `MotionValue` was updated.
|
|
252
|
-
*
|
|
253
|
-
* @internal
|
|
254
|
-
*/
|
|
255
|
-
this.lastUpdated = 0;
|
|
256
|
-
/**
|
|
257
|
-
* Functions to notify when the `MotionValue` updates.
|
|
258
|
-
*
|
|
259
|
-
* @internal
|
|
260
|
-
*/
|
|
261
|
-
this.updateSubscribers = new SubscriptionManager();
|
|
262
|
-
/**
|
|
263
|
-
* Functions to notify when the velocity updates.
|
|
264
|
-
*
|
|
265
|
-
* @internal
|
|
266
|
-
*/
|
|
267
|
-
this.velocityUpdateSubscribers = new SubscriptionManager();
|
|
268
|
-
/**
|
|
269
|
-
* Functions to notify when the `MotionValue` updates and `render` is set to `true`.
|
|
270
|
-
*
|
|
271
|
-
* @internal
|
|
272
|
-
*/
|
|
273
|
-
this.renderSubscribers = new SubscriptionManager();
|
|
274
|
-
/**
|
|
275
|
-
* Tracks whether this value can output a velocity. Currently this is only true
|
|
276
|
-
* if the value is numerical, but we might be able to widen the scope here and support
|
|
277
|
-
* other value types.
|
|
278
|
-
*
|
|
279
|
-
* @internal
|
|
280
|
-
*/
|
|
281
|
-
this.canTrackVelocity = false;
|
|
282
|
-
this.updateAndNotify = (v, render = true) => {
|
|
283
|
-
this.prev = this.current;
|
|
284
|
-
this.current = v;
|
|
285
|
-
// Update timestamp
|
|
286
|
-
const { delta, timestamp } = frameData;
|
|
287
|
-
if (this.lastUpdated !== timestamp) {
|
|
288
|
-
this.timeDelta = delta;
|
|
289
|
-
this.lastUpdated = timestamp;
|
|
290
|
-
sync.postRender(this.scheduleVelocityCheck);
|
|
291
|
-
}
|
|
292
|
-
// Update update subscribers
|
|
293
|
-
if (this.prev !== this.current) {
|
|
294
|
-
this.updateSubscribers.notify(this.current);
|
|
295
|
-
}
|
|
296
|
-
// Update velocity subscribers
|
|
297
|
-
if (this.velocityUpdateSubscribers.getSize()) {
|
|
298
|
-
this.velocityUpdateSubscribers.notify(this.getVelocity());
|
|
299
|
-
}
|
|
300
|
-
// Update render subscribers
|
|
301
|
-
if (render) {
|
|
302
|
-
this.renderSubscribers.notify(this.current);
|
|
303
|
-
}
|
|
304
|
-
};
|
|
305
|
-
/**
|
|
306
|
-
* Schedule a velocity check for the next frame.
|
|
307
|
-
*
|
|
308
|
-
* This is an instanced and bound function to prevent generating a new
|
|
309
|
-
* function once per frame.
|
|
310
|
-
*
|
|
311
|
-
* @internal
|
|
312
|
-
*/
|
|
313
|
-
this.scheduleVelocityCheck = () => sync.postRender(this.velocityCheck);
|
|
314
|
-
/**
|
|
315
|
-
* Updates `prev` with `current` if the value hasn't been updated this frame.
|
|
316
|
-
* This ensures velocity calculations return `0`.
|
|
317
|
-
*
|
|
318
|
-
* This is an instanced and bound function to prevent generating a new
|
|
319
|
-
* function once per frame.
|
|
320
|
-
*
|
|
321
|
-
* @internal
|
|
322
|
-
*/
|
|
323
|
-
this.velocityCheck = ({ timestamp }) => {
|
|
324
|
-
if (timestamp !== this.lastUpdated) {
|
|
325
|
-
this.prev = this.current;
|
|
326
|
-
this.velocityUpdateSubscribers.notify(this.getVelocity());
|
|
327
|
-
}
|
|
328
|
-
};
|
|
329
|
-
this.hasAnimated = false;
|
|
330
|
-
this.prev = this.current = init;
|
|
331
|
-
this.canTrackVelocity = isFloat(this.current);
|
|
385
|
+
// Adapted from https://gist.github.com/mjackson/5311256
|
|
386
|
+
function hueToRgb(p, q, t) {
|
|
387
|
+
if (t < 0)
|
|
388
|
+
t += 1;
|
|
389
|
+
if (t > 1)
|
|
390
|
+
t -= 1;
|
|
391
|
+
if (t < 1 / 6)
|
|
392
|
+
return p + (q - p) * 6 * t;
|
|
393
|
+
if (t < 1 / 2)
|
|
394
|
+
return q;
|
|
395
|
+
if (t < 2 / 3)
|
|
396
|
+
return p + (q - p) * (2 / 3 - t) * 6;
|
|
397
|
+
return p;
|
|
398
|
+
}
|
|
399
|
+
function hslaToRgba({ hue, saturation, lightness, alpha }) {
|
|
400
|
+
hue /= 360;
|
|
401
|
+
saturation /= 100;
|
|
402
|
+
lightness /= 100;
|
|
403
|
+
let red = 0;
|
|
404
|
+
let green = 0;
|
|
405
|
+
let blue = 0;
|
|
406
|
+
if (!saturation) {
|
|
407
|
+
red = green = blue = lightness;
|
|
332
408
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
*
|
|
342
|
-
* ```jsx
|
|
343
|
-
* export const MyComponent = () => {
|
|
344
|
-
* const x = useMotionValue(0)
|
|
345
|
-
* const y = useMotionValue(0)
|
|
346
|
-
* const opacity = useMotionValue(1)
|
|
347
|
-
*
|
|
348
|
-
* useEffect(() => {
|
|
349
|
-
* function updateOpacity() {
|
|
350
|
-
* const maxXY = Math.max(x.get(), y.get())
|
|
351
|
-
* const newOpacity = transform(maxXY, [0, 100], [1, 0])
|
|
352
|
-
* opacity.set(newOpacity)
|
|
353
|
-
* }
|
|
354
|
-
*
|
|
355
|
-
* const unsubscribeX = x.onChange(updateOpacity)
|
|
356
|
-
* const unsubscribeY = y.onChange(updateOpacity)
|
|
357
|
-
*
|
|
358
|
-
* return () => {
|
|
359
|
-
* unsubscribeX()
|
|
360
|
-
* unsubscribeY()
|
|
361
|
-
* }
|
|
362
|
-
* }, [])
|
|
363
|
-
*
|
|
364
|
-
* return <motion.div style={{ x }} />
|
|
365
|
-
* }
|
|
366
|
-
* ```
|
|
367
|
-
*
|
|
368
|
-
* @privateRemarks
|
|
369
|
-
*
|
|
370
|
-
* We could look into a `useOnChange` hook if the above lifecycle management proves confusing.
|
|
371
|
-
*
|
|
372
|
-
* ```jsx
|
|
373
|
-
* useOnChange(x, () => {})
|
|
374
|
-
* ```
|
|
375
|
-
*
|
|
376
|
-
* @param subscriber - A function that receives the latest value.
|
|
377
|
-
* @returns A function that, when called, will cancel this subscription.
|
|
378
|
-
*
|
|
379
|
-
* @public
|
|
380
|
-
*/
|
|
381
|
-
onChange(subscription) {
|
|
382
|
-
return this.updateSubscribers.add(subscription);
|
|
409
|
+
else {
|
|
410
|
+
const q = lightness < 0.5
|
|
411
|
+
? lightness * (1 + saturation)
|
|
412
|
+
: lightness + saturation - lightness * saturation;
|
|
413
|
+
const p = 2 * lightness - q;
|
|
414
|
+
red = hueToRgb(p, q, hue + 1 / 3);
|
|
415
|
+
green = hueToRgb(p, q, hue);
|
|
416
|
+
blue = hueToRgb(p, q, hue - 1 / 3);
|
|
383
417
|
}
|
|
384
|
-
|
|
385
|
-
|
|
418
|
+
return {
|
|
419
|
+
red: Math.round(red * 255),
|
|
420
|
+
green: Math.round(green * 255),
|
|
421
|
+
blue: Math.round(blue * 255),
|
|
422
|
+
alpha,
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Linear color space blending
|
|
427
|
+
// Explained https://www.youtube.com/watch?v=LKnqECcg6Gw
|
|
428
|
+
// Demonstrated http://codepen.io/osublake/pen/xGVVaN
|
|
429
|
+
const mixLinearColor = (from, to, v) => {
|
|
430
|
+
const fromExpo = from * from;
|
|
431
|
+
return Math.sqrt(Math.max(0, v * (to * to - fromExpo) + fromExpo));
|
|
432
|
+
};
|
|
433
|
+
const colorTypes = [hex, rgba, hsla];
|
|
434
|
+
const getColorType = (v) => colorTypes.find((type) => type.test(v));
|
|
435
|
+
function asRGBA(color) {
|
|
436
|
+
const type = getColorType(color);
|
|
437
|
+
invariant(Boolean(type), `'${color}' is not an animatable color. Use the equivalent color code instead.`);
|
|
438
|
+
let model = type.parse(color);
|
|
439
|
+
if (type === hsla) {
|
|
440
|
+
// TODO Remove this cast - needed since Framer Motion's stricter typing
|
|
441
|
+
model = hslaToRgba(model);
|
|
386
442
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
return
|
|
443
|
+
return model;
|
|
444
|
+
}
|
|
445
|
+
const mixColor = (from, to) => {
|
|
446
|
+
const fromRGBA = asRGBA(from);
|
|
447
|
+
const toRGBA = asRGBA(to);
|
|
448
|
+
const blended = { ...fromRGBA };
|
|
449
|
+
return (v) => {
|
|
450
|
+
blended.red = mixLinearColor(fromRGBA.red, toRGBA.red, v);
|
|
451
|
+
blended.green = mixLinearColor(fromRGBA.green, toRGBA.green, v);
|
|
452
|
+
blended.blue = mixLinearColor(fromRGBA.blue, toRGBA.blue, v);
|
|
453
|
+
blended.alpha = mix(fromRGBA.alpha, toRGBA.alpha, v);
|
|
454
|
+
return rgba.transform(blended);
|
|
455
|
+
};
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Pipe
|
|
460
|
+
* Compose other transformers to run linearily
|
|
461
|
+
* pipe(min(20), max(40))
|
|
462
|
+
* @param {...functions} transformers
|
|
463
|
+
* @return {function}
|
|
464
|
+
*/
|
|
465
|
+
const combineFunctions = (a, b) => (v) => b(a(v));
|
|
466
|
+
const pipe = (...transformers) => transformers.reduce(combineFunctions);
|
|
467
|
+
|
|
468
|
+
const colorToken = "${c}";
|
|
469
|
+
const numberToken = "${n}";
|
|
470
|
+
function test(v) {
|
|
471
|
+
var _a, _b;
|
|
472
|
+
return (isNaN(v) &&
|
|
473
|
+
isString(v) &&
|
|
474
|
+
(((_a = v.match(floatRegex)) === null || _a === void 0 ? void 0 : _a.length) || 0) +
|
|
475
|
+
(((_b = v.match(colorRegex)) === null || _b === void 0 ? void 0 : _b.length) || 0) >
|
|
476
|
+
0);
|
|
477
|
+
}
|
|
478
|
+
function analyseComplexValue(v) {
|
|
479
|
+
if (typeof v === "number")
|
|
480
|
+
v = `${v}`;
|
|
481
|
+
const values = [];
|
|
482
|
+
let numColors = 0;
|
|
483
|
+
let numNumbers = 0;
|
|
484
|
+
const colors = v.match(colorRegex);
|
|
485
|
+
if (colors) {
|
|
486
|
+
numColors = colors.length;
|
|
487
|
+
// Strip colors from input so they're not picked up by number regex.
|
|
488
|
+
// There's a better way to combine these regex searches, but its beyond my regex skills
|
|
489
|
+
v = v.replace(colorRegex, colorToken);
|
|
490
|
+
values.push(...colors.map(color.parse));
|
|
399
491
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
attach(passiveEffect) {
|
|
406
|
-
this.passiveEffect = passiveEffect;
|
|
492
|
+
const numbers = v.match(floatRegex);
|
|
493
|
+
if (numbers) {
|
|
494
|
+
numNumbers = numbers.length;
|
|
495
|
+
v = v.replace(floatRegex, numberToken);
|
|
496
|
+
values.push(...numbers.map(number.parse));
|
|
407
497
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
*/
|
|
423
|
-
set(v, render = true) {
|
|
424
|
-
if (!render || !this.passiveEffect) {
|
|
425
|
-
this.updateAndNotify(v, render);
|
|
426
|
-
}
|
|
427
|
-
else {
|
|
428
|
-
this.passiveEffect(v, this.updateAndNotify);
|
|
498
|
+
return { values, numColors, numNumbers, tokenised: v };
|
|
499
|
+
}
|
|
500
|
+
function parse(v) {
|
|
501
|
+
return analyseComplexValue(v).values;
|
|
502
|
+
}
|
|
503
|
+
function createTransformer(source) {
|
|
504
|
+
const { values, numColors, tokenised } = analyseComplexValue(source);
|
|
505
|
+
const numValues = values.length;
|
|
506
|
+
return (v) => {
|
|
507
|
+
let output = tokenised;
|
|
508
|
+
for (let i = 0; i < numValues; i++) {
|
|
509
|
+
output = output.replace(i < numColors ? colorToken : numberToken, i < numColors
|
|
510
|
+
? color.transform(v[i])
|
|
511
|
+
: sanitize(v[i]));
|
|
429
512
|
}
|
|
513
|
+
return output;
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
const convertNumbersToZero = (v) => typeof v === "number" ? 0 : v;
|
|
517
|
+
function getAnimatableNone$1(v) {
|
|
518
|
+
const parsed = parse(v);
|
|
519
|
+
const transformer = createTransformer(v);
|
|
520
|
+
return transformer(parsed.map(convertNumbersToZero));
|
|
521
|
+
}
|
|
522
|
+
const complex = { test, parse, createTransformer, getAnimatableNone: getAnimatableNone$1 };
|
|
523
|
+
|
|
524
|
+
function getMixer(origin, target) {
|
|
525
|
+
if (typeof origin === "number") {
|
|
526
|
+
return (v) => mix(origin, target, v);
|
|
430
527
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
*
|
|
434
|
-
* @returns - The latest state of `MotionValue`
|
|
435
|
-
*
|
|
436
|
-
* @public
|
|
437
|
-
*/
|
|
438
|
-
get() {
|
|
439
|
-
return this.current;
|
|
528
|
+
else if (color.test(origin)) {
|
|
529
|
+
return mixColor(origin, target);
|
|
440
530
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
*/
|
|
444
|
-
getPrevious() {
|
|
445
|
-
return this.prev;
|
|
531
|
+
else {
|
|
532
|
+
return mixComplex(origin, target);
|
|
446
533
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
return
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
534
|
+
}
|
|
535
|
+
const mixArray = (from, to) => {
|
|
536
|
+
const output = [...from];
|
|
537
|
+
const numValues = output.length;
|
|
538
|
+
const blendValue = from.map((fromThis, i) => getMixer(fromThis, to[i]));
|
|
539
|
+
return (v) => {
|
|
540
|
+
for (let i = 0; i < numValues; i++) {
|
|
541
|
+
output[i] = blendValue[i](v);
|
|
542
|
+
}
|
|
543
|
+
return output;
|
|
544
|
+
};
|
|
545
|
+
};
|
|
546
|
+
const mixObject = (origin, target) => {
|
|
547
|
+
const output = { ...origin, ...target };
|
|
548
|
+
const blendValue = {};
|
|
549
|
+
for (const key in output) {
|
|
550
|
+
if (origin[key] !== undefined && target[key] !== undefined) {
|
|
551
|
+
blendValue[key] = getMixer(origin[key], target[key]);
|
|
552
|
+
}
|
|
461
553
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
return
|
|
477
|
-
this.hasAnimated = true;
|
|
478
|
-
this.stopAnimation = animation(resolve);
|
|
479
|
-
}).then(() => this.clearAnimation());
|
|
554
|
+
return (v) => {
|
|
555
|
+
for (const key in blendValue) {
|
|
556
|
+
output[key] = blendValue[key](v);
|
|
557
|
+
}
|
|
558
|
+
return output;
|
|
559
|
+
};
|
|
560
|
+
};
|
|
561
|
+
const mixComplex = (origin, target) => {
|
|
562
|
+
const template = complex.createTransformer(target);
|
|
563
|
+
const originStats = analyseComplexValue(origin);
|
|
564
|
+
const targetStats = analyseComplexValue(target);
|
|
565
|
+
const canInterpolate = originStats.numColors === targetStats.numColors &&
|
|
566
|
+
originStats.numNumbers >= targetStats.numNumbers;
|
|
567
|
+
if (canInterpolate) {
|
|
568
|
+
return pipe(mixArray(originStats.values, targetStats.values), template);
|
|
480
569
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
* @public
|
|
485
|
-
*/
|
|
486
|
-
stop() {
|
|
487
|
-
if (this.stopAnimation)
|
|
488
|
-
this.stopAnimation();
|
|
489
|
-
this.clearAnimation();
|
|
570
|
+
else {
|
|
571
|
+
warning(true, `Complex values '${origin}' and '${target}' too different to mix. Ensure all colors are of the same type, and that each contains the same quantity of number and color values. Falling back to instant transition.`);
|
|
572
|
+
return (p) => `${p > 0 ? target : origin}`;
|
|
490
573
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
/*
|
|
577
|
+
Progress within given range
|
|
578
|
+
|
|
579
|
+
Given a lower limit and an upper limit, we return the progress
|
|
580
|
+
(expressed as a number 0-1) represented by the given value, and
|
|
581
|
+
limit that progress to within 0-1.
|
|
582
|
+
|
|
583
|
+
@param [number]: Lower limit
|
|
584
|
+
@param [number]: Upper limit
|
|
585
|
+
@param [number]: Value to find progress within given range
|
|
586
|
+
@return [number]: Progress of value within range as expressed 0-1
|
|
587
|
+
*/
|
|
588
|
+
const progress = (from, to, value) => {
|
|
589
|
+
const toFromDifference = to - from;
|
|
590
|
+
return toFromDifference === 0 ? 1 : (value - from) / toFromDifference;
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
const mixNumber = (from, to) => (p) => mix(from, to, p);
|
|
594
|
+
function detectMixerFactory(v) {
|
|
595
|
+
if (typeof v === "number") {
|
|
596
|
+
return mixNumber;
|
|
498
597
|
}
|
|
499
|
-
|
|
500
|
-
|
|
598
|
+
else if (typeof v === "string") {
|
|
599
|
+
if (color.test(v)) {
|
|
600
|
+
return mixColor;
|
|
601
|
+
}
|
|
602
|
+
else {
|
|
603
|
+
return mixComplex;
|
|
604
|
+
}
|
|
501
605
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
*
|
|
505
|
-
* The `MotionValue` hooks like `useMotionValue` and `useTransform` automatically
|
|
506
|
-
* handle the lifecycle of the returned `MotionValue`, so this method is only necessary if you've manually
|
|
507
|
-
* created a `MotionValue` via the `motionValue` function.
|
|
508
|
-
*
|
|
509
|
-
* @public
|
|
510
|
-
*/
|
|
511
|
-
destroy() {
|
|
512
|
-
this.updateSubscribers.clear();
|
|
513
|
-
this.renderSubscribers.clear();
|
|
514
|
-
this.stop();
|
|
606
|
+
else if (Array.isArray(v)) {
|
|
607
|
+
return mixArray;
|
|
515
608
|
}
|
|
609
|
+
else if (typeof v === "object") {
|
|
610
|
+
return mixObject;
|
|
611
|
+
}
|
|
612
|
+
return mixNumber;
|
|
516
613
|
}
|
|
517
|
-
function
|
|
518
|
-
|
|
614
|
+
function createMixers(output, ease, customMixer) {
|
|
615
|
+
const mixers = [];
|
|
616
|
+
const mixerFactory = customMixer || detectMixerFactory(output[0]);
|
|
617
|
+
const numMixers = output.length - 1;
|
|
618
|
+
for (let i = 0; i < numMixers; i++) {
|
|
619
|
+
let mixer = mixerFactory(output[i], output[i + 1]);
|
|
620
|
+
if (ease) {
|
|
621
|
+
const easingFunction = Array.isArray(ease) ? ease[i] : ease;
|
|
622
|
+
mixer = pipe(easingFunction, mixer);
|
|
623
|
+
}
|
|
624
|
+
mixers.push(mixer);
|
|
625
|
+
}
|
|
626
|
+
return mixers;
|
|
519
627
|
}
|
|
520
|
-
|
|
521
|
-
const isMotionValue = (value) => !!(value === null || value === void 0 ? void 0 : value.getVelocity);
|
|
522
|
-
|
|
523
628
|
/**
|
|
524
|
-
*
|
|
629
|
+
* Create a function that maps from a numerical input array to a generic output array.
|
|
525
630
|
*
|
|
526
|
-
*
|
|
527
|
-
*
|
|
631
|
+
* Accepts:
|
|
632
|
+
* - Numbers
|
|
633
|
+
* - Colors (hex, hsl, hsla, rgb, rgba)
|
|
634
|
+
* - Complex (combinations of one or more numbers or strings)
|
|
635
|
+
*
|
|
636
|
+
* ```jsx
|
|
637
|
+
* const mixColor = interpolate([0, 1], ['#fff', '#000'])
|
|
638
|
+
*
|
|
639
|
+
* mixColor(0.5) // 'rgba(128, 128, 128, 1)'
|
|
640
|
+
* ```
|
|
641
|
+
*
|
|
642
|
+
* TODO Revist this approach once we've moved to data models for values,
|
|
643
|
+
* probably not needed to pregenerate mixer functions.
|
|
644
|
+
*
|
|
645
|
+
* @public
|
|
528
646
|
*/
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
647
|
+
function interpolate(input, output, { clamp: isClamp = true, ease, mixer } = {}) {
|
|
648
|
+
const inputLength = input.length;
|
|
649
|
+
invariant(inputLength === output.length, "Both input and output ranges must be the same length");
|
|
650
|
+
invariant(!ease || !Array.isArray(ease) || ease.length === inputLength - 1, "Array of easing functions must be of length `input.length - 1`, as it applies to the transitions **between** the defined values.");
|
|
651
|
+
// If input runs highest -> lowest, reverse both arrays
|
|
652
|
+
if (input[0] > input[inputLength - 1]) {
|
|
653
|
+
input = [...input].reverse();
|
|
654
|
+
output = [...output].reverse();
|
|
655
|
+
}
|
|
656
|
+
const mixers = createMixers(output, ease, mixer);
|
|
657
|
+
const numMixers = mixers.length;
|
|
658
|
+
const interpolator = (v) => {
|
|
659
|
+
let i = 0;
|
|
660
|
+
if (numMixers > 1) {
|
|
661
|
+
for (; i < input.length - 2; i++) {
|
|
662
|
+
if (v < input[i + 1])
|
|
663
|
+
break;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
const progressInRange = progress(input[i], input[i + 1], v);
|
|
667
|
+
return mixers[i](progressInRange);
|
|
668
|
+
};
|
|
669
|
+
return isClamp
|
|
670
|
+
? (v) => interpolator(clamp(input[0], input[inputLength - 1], v))
|
|
671
|
+
: interpolator;
|
|
544
672
|
}
|
|
545
673
|
|
|
546
674
|
const noop = (any) => any;
|
|
@@ -593,311 +721,678 @@
|
|
|
593
721
|
return (t) => t === 0 || t === 1 ? t : calcBezier(getTForX(t), mY1, mY2);
|
|
594
722
|
}
|
|
595
723
|
|
|
596
|
-
// Accepts an easing function and returns a new one that outputs mirrored values for
|
|
597
|
-
// the second half of the animation. Turns easeIn into easeInOut.
|
|
598
|
-
const mirrorEasing = (easing) => (p) => p <= 0.5 ? easing(2 * p) / 2 : (2 - easing(2 * (1 - p))) / 2;
|
|
599
|
-
|
|
600
|
-
// Accepts an easing function and returns a new one that outputs reversed values.
|
|
601
|
-
// Turns easeIn into easeOut.
|
|
602
|
-
const reverseEasing = (easing) => (p) => 1 - easing(1 - p);
|
|
603
|
-
|
|
604
|
-
const easeIn = (p) => p * p;
|
|
605
|
-
const easeOut = reverseEasing(easeIn);
|
|
606
|
-
const easeInOut = mirrorEasing(easeIn);
|
|
607
|
-
|
|
608
724
|
const circIn = (p) => 1 - Math.sin(Math.acos(p));
|
|
609
725
|
const circOut = reverseEasing(circIn);
|
|
610
726
|
const circInOut = mirrorEasing(circOut);
|
|
611
727
|
|
|
612
|
-
const createBackIn = (power = 1.525) => (p) => p * p * ((power + 1) * p - power);
|
|
613
|
-
const backIn = createBackIn();
|
|
614
|
-
const backOut = reverseEasing(backIn);
|
|
615
|
-
const backInOut = mirrorEasing(backIn);
|
|
728
|
+
const createBackIn = (power = 1.525) => (p) => p * p * ((power + 1) * p - power);
|
|
729
|
+
const backIn = createBackIn();
|
|
730
|
+
const backOut = reverseEasing(backIn);
|
|
731
|
+
const backInOut = mirrorEasing(backIn);
|
|
732
|
+
|
|
733
|
+
const createAnticipate = (power) => {
|
|
734
|
+
const backEasing = createBackIn(power);
|
|
735
|
+
return (p) => (p *= 2) < 1
|
|
736
|
+
? 0.5 * backEasing(p)
|
|
737
|
+
: 0.5 * (2 - Math.pow(2, -10 * (p - 1)));
|
|
738
|
+
};
|
|
739
|
+
const anticipate = createAnticipate();
|
|
740
|
+
|
|
741
|
+
const easingLookup = {
|
|
742
|
+
linear: noop,
|
|
743
|
+
easeIn,
|
|
744
|
+
easeInOut,
|
|
745
|
+
easeOut,
|
|
746
|
+
circIn,
|
|
747
|
+
circInOut,
|
|
748
|
+
circOut,
|
|
749
|
+
backIn,
|
|
750
|
+
backInOut,
|
|
751
|
+
backOut,
|
|
752
|
+
anticipate,
|
|
753
|
+
};
|
|
754
|
+
const easingDefinitionToFunction = (definition) => {
|
|
755
|
+
if (Array.isArray(definition)) {
|
|
756
|
+
// If cubic bezier definition, create bezier curve
|
|
757
|
+
invariant(definition.length === 4, `Cubic bezier arrays must contain four numerical values.`);
|
|
758
|
+
const [x1, y1, x2, y2] = definition;
|
|
759
|
+
return cubicBezier(x1, y1, x2, y2);
|
|
760
|
+
}
|
|
761
|
+
else if (typeof definition === "string") {
|
|
762
|
+
// Else lookup from table
|
|
763
|
+
invariant(easingLookup[definition] !== undefined, `Invalid easing type '${definition}'`);
|
|
764
|
+
return easingLookup[definition];
|
|
765
|
+
}
|
|
766
|
+
return definition;
|
|
767
|
+
};
|
|
768
|
+
const isEasingArray = (ease) => {
|
|
769
|
+
return Array.isArray(ease) && typeof ease[0] !== "number";
|
|
770
|
+
};
|
|
771
|
+
|
|
772
|
+
function defaultEasing(values, easing) {
|
|
773
|
+
return values.map(() => easing || easeInOut).splice(0, values.length - 1);
|
|
774
|
+
}
|
|
775
|
+
function defaultOffset(values) {
|
|
776
|
+
const numValues = values.length;
|
|
777
|
+
return values.map((_value, i) => i !== 0 ? i / (numValues - 1) : 0);
|
|
778
|
+
}
|
|
779
|
+
function convertOffsetToTimes(offset, duration) {
|
|
780
|
+
return offset.map((o) => o * duration);
|
|
781
|
+
}
|
|
782
|
+
function keyframes({ keyframes: keyframeValues, ease = easeInOut, times, duration = 300, }) {
|
|
783
|
+
keyframeValues = [...keyframeValues];
|
|
784
|
+
const origin = keyframes[0];
|
|
785
|
+
/**
|
|
786
|
+
* Easing functions can be externally defined as strings. Here we convert them
|
|
787
|
+
* into actual functions.
|
|
788
|
+
*/
|
|
789
|
+
const easingFunctions = isEasingArray(ease)
|
|
790
|
+
? ease.map(easingDefinitionToFunction)
|
|
791
|
+
: easingDefinitionToFunction(ease);
|
|
792
|
+
/**
|
|
793
|
+
* This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
|
|
794
|
+
* to reduce GC during animation.
|
|
795
|
+
*/
|
|
796
|
+
const state = { done: false, value: origin };
|
|
797
|
+
/**
|
|
798
|
+
* Create a times array based on the provided 0-1 offsets
|
|
799
|
+
*/
|
|
800
|
+
const absoluteTimes = convertOffsetToTimes(
|
|
801
|
+
// Only use the provided offsets if they're the correct length
|
|
802
|
+
// TODO Maybe we should warn here if there's a length mismatch
|
|
803
|
+
times && times.length === keyframes.length
|
|
804
|
+
? times
|
|
805
|
+
: defaultOffset(keyframeValues), duration);
|
|
806
|
+
function createInterpolator() {
|
|
807
|
+
return interpolate(absoluteTimes, keyframeValues, {
|
|
808
|
+
ease: Array.isArray(easingFunctions)
|
|
809
|
+
? easingFunctions
|
|
810
|
+
: defaultEasing(keyframeValues, easingFunctions),
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
let interpolator = createInterpolator();
|
|
814
|
+
return {
|
|
815
|
+
next: (t) => {
|
|
816
|
+
state.value = interpolator(t);
|
|
817
|
+
state.done = t >= duration;
|
|
818
|
+
return state;
|
|
819
|
+
},
|
|
820
|
+
flipTarget: () => {
|
|
821
|
+
keyframeValues.reverse();
|
|
822
|
+
interpolator = createInterpolator();
|
|
823
|
+
},
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
const safeMin = 0.001;
|
|
828
|
+
const minDuration = 0.01;
|
|
829
|
+
const maxDuration = 10.0;
|
|
830
|
+
const minDamping = 0.05;
|
|
831
|
+
const maxDamping = 1;
|
|
832
|
+
function findSpring({ duration = 800, bounce = 0.25, velocity = 0, mass = 1, }) {
|
|
833
|
+
let envelope;
|
|
834
|
+
let derivative;
|
|
835
|
+
warning(duration <= maxDuration * 1000, "Spring duration must be 10 seconds or less");
|
|
836
|
+
let dampingRatio = 1 - bounce;
|
|
837
|
+
/**
|
|
838
|
+
* Restrict dampingRatio and duration to within acceptable ranges.
|
|
839
|
+
*/
|
|
840
|
+
dampingRatio = clamp(minDamping, maxDamping, dampingRatio);
|
|
841
|
+
duration = clamp(minDuration, maxDuration, duration / 1000);
|
|
842
|
+
if (dampingRatio < 1) {
|
|
843
|
+
/**
|
|
844
|
+
* Underdamped spring
|
|
845
|
+
*/
|
|
846
|
+
envelope = (undampedFreq) => {
|
|
847
|
+
const exponentialDecay = undampedFreq * dampingRatio;
|
|
848
|
+
const delta = exponentialDecay * duration;
|
|
849
|
+
const a = exponentialDecay - velocity;
|
|
850
|
+
const b = calcAngularFreq(undampedFreq, dampingRatio);
|
|
851
|
+
const c = Math.exp(-delta);
|
|
852
|
+
return safeMin - (a / b) * c;
|
|
853
|
+
};
|
|
854
|
+
derivative = (undampedFreq) => {
|
|
855
|
+
const exponentialDecay = undampedFreq * dampingRatio;
|
|
856
|
+
const delta = exponentialDecay * duration;
|
|
857
|
+
const d = delta * velocity + velocity;
|
|
858
|
+
const e = Math.pow(dampingRatio, 2) * Math.pow(undampedFreq, 2) * duration;
|
|
859
|
+
const f = Math.exp(-delta);
|
|
860
|
+
const g = calcAngularFreq(Math.pow(undampedFreq, 2), dampingRatio);
|
|
861
|
+
const factor = -envelope(undampedFreq) + safeMin > 0 ? -1 : 1;
|
|
862
|
+
return (factor * ((d - e) * f)) / g;
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
else {
|
|
866
|
+
/**
|
|
867
|
+
* Critically-damped spring
|
|
868
|
+
*/
|
|
869
|
+
envelope = (undampedFreq) => {
|
|
870
|
+
const a = Math.exp(-undampedFreq * duration);
|
|
871
|
+
const b = (undampedFreq - velocity) * duration + 1;
|
|
872
|
+
return -safeMin + a * b;
|
|
873
|
+
};
|
|
874
|
+
derivative = (undampedFreq) => {
|
|
875
|
+
const a = Math.exp(-undampedFreq * duration);
|
|
876
|
+
const b = (velocity - undampedFreq) * (duration * duration);
|
|
877
|
+
return a * b;
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
const initialGuess = 5 / duration;
|
|
881
|
+
const undampedFreq = approximateRoot(envelope, derivative, initialGuess);
|
|
882
|
+
duration = duration * 1000;
|
|
883
|
+
if (isNaN(undampedFreq)) {
|
|
884
|
+
return {
|
|
885
|
+
stiffness: 100,
|
|
886
|
+
damping: 10,
|
|
887
|
+
duration,
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
else {
|
|
891
|
+
const stiffness = Math.pow(undampedFreq, 2) * mass;
|
|
892
|
+
return {
|
|
893
|
+
stiffness,
|
|
894
|
+
damping: dampingRatio * 2 * Math.sqrt(mass * stiffness),
|
|
895
|
+
duration,
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
const rootIterations = 12;
|
|
900
|
+
function approximateRoot(envelope, derivative, initialGuess) {
|
|
901
|
+
let result = initialGuess;
|
|
902
|
+
for (let i = 1; i < rootIterations; i++) {
|
|
903
|
+
result = result - envelope(result) / derivative(result);
|
|
904
|
+
}
|
|
905
|
+
return result;
|
|
906
|
+
}
|
|
907
|
+
function calcAngularFreq(undampedFreq, dampingRatio) {
|
|
908
|
+
return undampedFreq * Math.sqrt(1 - dampingRatio * dampingRatio);
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/*
|
|
912
|
+
Convert velocity into velocity per second
|
|
616
913
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
}
|
|
623
|
-
const anticipate = createAnticipate();
|
|
914
|
+
@param [number]: Unit per frame
|
|
915
|
+
@param [number]: Frame duration in ms
|
|
916
|
+
*/
|
|
917
|
+
function velocityPerSecond(velocity, frameDuration) {
|
|
918
|
+
return frameDuration ? velocity * (1000 / frameDuration) : 0;
|
|
919
|
+
}
|
|
624
920
|
|
|
625
|
-
const
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
const
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
921
|
+
const durationKeys = ["duration", "bounce"];
|
|
922
|
+
const physicsKeys = ["stiffness", "damping", "mass"];
|
|
923
|
+
function isSpringType(options, keys) {
|
|
924
|
+
return keys.some((key) => options[key] !== undefined);
|
|
925
|
+
}
|
|
926
|
+
function getSpringOptions(options) {
|
|
927
|
+
let springOptions = {
|
|
928
|
+
velocity: 0.0,
|
|
929
|
+
stiffness: 100,
|
|
930
|
+
damping: 10,
|
|
931
|
+
mass: 1.0,
|
|
932
|
+
isResolvedFromDuration: false,
|
|
933
|
+
...options,
|
|
934
|
+
};
|
|
935
|
+
// stiffness/damping/mass overrides duration/bounce
|
|
936
|
+
if (!isSpringType(options, physicsKeys) &&
|
|
937
|
+
isSpringType(options, durationKeys)) {
|
|
938
|
+
const derived = findSpring(options);
|
|
939
|
+
springOptions = {
|
|
940
|
+
...springOptions,
|
|
941
|
+
...derived,
|
|
942
|
+
velocity: 0.0,
|
|
943
|
+
mass: 1.0,
|
|
944
|
+
};
|
|
945
|
+
springOptions.isResolvedFromDuration = true;
|
|
649
946
|
}
|
|
650
|
-
return
|
|
651
|
-
}
|
|
652
|
-
const
|
|
653
|
-
return Array.isArray(ease) && typeof ease[0] !== "number";
|
|
654
|
-
};
|
|
655
|
-
|
|
947
|
+
return springOptions;
|
|
948
|
+
}
|
|
949
|
+
const velocitySampleDuration = 5;
|
|
656
950
|
/**
|
|
657
|
-
*
|
|
658
|
-
* everything in this folder should probably be referred to as models vs types
|
|
951
|
+
* This is based on the spring implementation of Wobble https://github.com/skevy/wobble
|
|
659
952
|
*/
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
953
|
+
function spring({ keyframes, restSpeed = 2, restDelta = 0.01, ...options }) {
|
|
954
|
+
let origin = keyframes[0];
|
|
955
|
+
let target = keyframes[keyframes.length - 1];
|
|
956
|
+
/**
|
|
957
|
+
* This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
|
|
958
|
+
* to reduce GC during animation.
|
|
959
|
+
*/
|
|
960
|
+
const state = { done: false, value: origin };
|
|
961
|
+
const { stiffness, damping, mass, velocity, duration, isResolvedFromDuration, } = getSpringOptions(options);
|
|
962
|
+
let resolveSpring = zero;
|
|
963
|
+
let initialVelocity = velocity ? -(velocity / 1000) : 0.0;
|
|
964
|
+
const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass));
|
|
965
|
+
function createSpring() {
|
|
966
|
+
const initialDelta = target - origin;
|
|
967
|
+
const undampedAngularFreq = Math.sqrt(stiffness / mass) / 1000;
|
|
968
|
+
/**
|
|
969
|
+
* If we're working within what looks like a 0-1 range, change the default restDelta
|
|
970
|
+
* to 0.01
|
|
971
|
+
*/
|
|
972
|
+
if (restDelta === undefined) {
|
|
973
|
+
restDelta = Math.min(Math.abs(target - origin) / 100, 0.4);
|
|
974
|
+
}
|
|
975
|
+
if (dampingRatio < 1) {
|
|
976
|
+
const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
|
|
977
|
+
// Underdamped spring
|
|
978
|
+
resolveSpring = (t) => {
|
|
979
|
+
const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
|
|
980
|
+
return (target -
|
|
981
|
+
envelope *
|
|
982
|
+
(((initialVelocity +
|
|
983
|
+
dampingRatio * undampedAngularFreq * initialDelta) /
|
|
984
|
+
angularFreq) *
|
|
985
|
+
Math.sin(angularFreq * t) +
|
|
986
|
+
initialDelta * Math.cos(angularFreq * t)));
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
else if (dampingRatio === 1) {
|
|
990
|
+
// Critically damped spring
|
|
991
|
+
resolveSpring = (t) => target -
|
|
992
|
+
Math.exp(-undampedAngularFreq * t) *
|
|
993
|
+
(initialDelta +
|
|
994
|
+
(initialVelocity + undampedAngularFreq * initialDelta) *
|
|
995
|
+
t);
|
|
996
|
+
}
|
|
997
|
+
else {
|
|
998
|
+
// Overdamped spring
|
|
999
|
+
const dampedAngularFreq = undampedAngularFreq * Math.sqrt(dampingRatio * dampingRatio - 1);
|
|
1000
|
+
resolveSpring = (t) => {
|
|
1001
|
+
const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
|
|
1002
|
+
// When performing sinh or cosh values can hit Infinity so we cap them here
|
|
1003
|
+
const freqForT = Math.min(dampedAngularFreq * t, 300);
|
|
1004
|
+
return (target -
|
|
1005
|
+
(envelope *
|
|
1006
|
+
((initialVelocity +
|
|
1007
|
+
dampingRatio * undampedAngularFreq * initialDelta) *
|
|
1008
|
+
Math.sinh(freqForT) +
|
|
1009
|
+
dampedAngularFreq *
|
|
1010
|
+
initialDelta *
|
|
1011
|
+
Math.cosh(freqForT))) /
|
|
1012
|
+
dampedAngularFreq);
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
createSpring();
|
|
1017
|
+
return {
|
|
1018
|
+
next: (t) => {
|
|
1019
|
+
const current = resolveSpring(t);
|
|
1020
|
+
if (!isResolvedFromDuration) {
|
|
1021
|
+
let currentVelocity = initialVelocity;
|
|
1022
|
+
if (t !== 0) {
|
|
1023
|
+
/**
|
|
1024
|
+
* We only need to calculate velocity for under-damped springs
|
|
1025
|
+
* as over- and critically-damped springs can't overshoot, so
|
|
1026
|
+
* checking only for displacement is enough.
|
|
1027
|
+
*/
|
|
1028
|
+
if (dampingRatio < 1) {
|
|
1029
|
+
const prevT = Math.max(0, t - velocitySampleDuration);
|
|
1030
|
+
currentVelocity = velocityPerSecond(current - resolveSpring(prevT), t - prevT);
|
|
1031
|
+
}
|
|
1032
|
+
else {
|
|
1033
|
+
currentVelocity = 0;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
const isBelowVelocityThreshold = Math.abs(currentVelocity) <= restSpeed;
|
|
1037
|
+
const isBelowDisplacementThreshold = Math.abs(target - current) <= restDelta;
|
|
1038
|
+
state.done =
|
|
1039
|
+
isBelowVelocityThreshold && isBelowDisplacementThreshold;
|
|
1040
|
+
}
|
|
1041
|
+
else {
|
|
1042
|
+
state.done = t >= duration;
|
|
1043
|
+
}
|
|
1044
|
+
state.value = state.done ? target : current;
|
|
1045
|
+
return state;
|
|
1046
|
+
},
|
|
1047
|
+
flipTarget: () => {
|
|
1048
|
+
initialVelocity = -initialVelocity;
|
|
1049
|
+
[origin, target] = [target, origin];
|
|
1050
|
+
createSpring();
|
|
1051
|
+
},
|
|
1052
|
+
};
|
|
668
1053
|
}
|
|
1054
|
+
spring.needsInterpolation = (a, b) => typeof a === "string" || typeof b === "string";
|
|
1055
|
+
const zero = (_t) => 0;
|
|
669
1056
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
const number = {
|
|
673
|
-
test: (v) => typeof v === "number",
|
|
674
|
-
parse: parseFloat,
|
|
675
|
-
transform: (v) => v,
|
|
676
|
-
};
|
|
677
|
-
const alpha = {
|
|
678
|
-
...number,
|
|
679
|
-
transform: (v) => clamp(0, 1, v),
|
|
680
|
-
};
|
|
681
|
-
const scale = {
|
|
682
|
-
...number,
|
|
683
|
-
default: 1,
|
|
684
|
-
};
|
|
685
|
-
|
|
1057
|
+
function decay({
|
|
686
1058
|
/**
|
|
687
|
-
*
|
|
688
|
-
*
|
|
1059
|
+
* The decay animation dynamically calculates an end of the animation
|
|
1060
|
+
* based on the initial keyframe, so we only need to define a single keyframe
|
|
1061
|
+
* as default.
|
|
689
1062
|
*/
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
1063
|
+
keyframes = [0], velocity = 0, power = 0.8, timeConstant = 350, restDelta = 0.5, modifyTarget, }) {
|
|
1064
|
+
const origin = keyframes[0];
|
|
1065
|
+
/**
|
|
1066
|
+
* This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
|
|
1067
|
+
* to reduce GC during animation.
|
|
1068
|
+
*/
|
|
1069
|
+
const state = { done: false, value: origin };
|
|
1070
|
+
let amplitude = power * velocity;
|
|
1071
|
+
const ideal = origin + amplitude;
|
|
1072
|
+
const target = modifyTarget === undefined ? ideal : modifyTarget(ideal);
|
|
1073
|
+
/**
|
|
1074
|
+
* If the target has changed we need to re-calculate the amplitude, otherwise
|
|
1075
|
+
* the animation will start from the wrong position.
|
|
1076
|
+
*/
|
|
1077
|
+
if (target !== ideal)
|
|
1078
|
+
amplitude = target - origin;
|
|
698
1079
|
return {
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
1080
|
+
next: (t) => {
|
|
1081
|
+
const delta = -amplitude * Math.exp(-t / timeConstant);
|
|
1082
|
+
state.done = !(delta > restDelta || delta < -restDelta);
|
|
1083
|
+
state.value = state.done ? target : target + delta;
|
|
1084
|
+
return state;
|
|
1085
|
+
},
|
|
1086
|
+
flipTarget: () => { },
|
|
703
1087
|
};
|
|
704
|
-
}
|
|
1088
|
+
}
|
|
705
1089
|
|
|
706
|
-
const
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
1090
|
+
const types = {
|
|
1091
|
+
decay,
|
|
1092
|
+
keyframes: keyframes,
|
|
1093
|
+
tween: keyframes,
|
|
1094
|
+
spring,
|
|
710
1095
|
};
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
1096
|
+
function loopElapsed(elapsed, duration, delay = 0) {
|
|
1097
|
+
return elapsed - duration - delay;
|
|
1098
|
+
}
|
|
1099
|
+
function reverseElapsed(elapsed, duration = 0, delay = 0, isForwardPlayback = true) {
|
|
1100
|
+
return isForwardPlayback
|
|
1101
|
+
? loopElapsed(duration + -elapsed, duration, delay)
|
|
1102
|
+
: duration - (elapsed - duration) + delay;
|
|
1103
|
+
}
|
|
1104
|
+
function hasRepeatDelayElapsed(elapsed, duration, delay, isForwardPlayback) {
|
|
1105
|
+
return isForwardPlayback ? elapsed >= duration + delay : elapsed <= -delay;
|
|
1106
|
+
}
|
|
1107
|
+
const framesync = (update) => {
|
|
1108
|
+
const passTimestamp = ({ delta }) => update(delta);
|
|
1109
|
+
return {
|
|
1110
|
+
start: () => sync.update(passTimestamp, true),
|
|
1111
|
+
stop: () => cancelSync.update(passTimestamp),
|
|
1112
|
+
};
|
|
723
1113
|
};
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
let
|
|
727
|
-
let
|
|
728
|
-
let
|
|
729
|
-
let
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
1114
|
+
function animate$1({ duration, driver = framesync, elapsed = 0, repeat: repeatMax = 0, repeatType = "loop", repeatDelay = 0, keyframes, autoplay = true, onPlay, onStop, onComplete, onRepeat, onUpdate, type = "keyframes", ...options }) {
|
|
1115
|
+
var _a, _b;
|
|
1116
|
+
let driverControls;
|
|
1117
|
+
let repeatCount = 0;
|
|
1118
|
+
let computedDuration = duration;
|
|
1119
|
+
let latest;
|
|
1120
|
+
let isComplete = false;
|
|
1121
|
+
let isForwardPlayback = true;
|
|
1122
|
+
let interpolateFromNumber;
|
|
1123
|
+
const animator = types[keyframes.length > 2 ? "keyframes" : type];
|
|
1124
|
+
const origin = keyframes[0];
|
|
1125
|
+
const target = keyframes[keyframes.length - 1];
|
|
1126
|
+
if ((_b = (_a = animator).needsInterpolation) === null || _b === void 0 ? void 0 : _b.call(_a, origin, target)) {
|
|
1127
|
+
interpolateFromNumber = interpolate([0, 100], [origin, target], {
|
|
1128
|
+
clamp: false,
|
|
1129
|
+
});
|
|
1130
|
+
keyframes = [0, 100];
|
|
737
1131
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
1132
|
+
const animation = animator({
|
|
1133
|
+
...options,
|
|
1134
|
+
duration,
|
|
1135
|
+
keyframes,
|
|
1136
|
+
});
|
|
1137
|
+
function repeat() {
|
|
1138
|
+
repeatCount++;
|
|
1139
|
+
if (repeatType === "reverse") {
|
|
1140
|
+
isForwardPlayback = repeatCount % 2 === 0;
|
|
1141
|
+
elapsed = reverseElapsed(elapsed, computedDuration, repeatDelay, isForwardPlayback);
|
|
1142
|
+
}
|
|
1143
|
+
else {
|
|
1144
|
+
elapsed = loopElapsed(elapsed, computedDuration, repeatDelay);
|
|
1145
|
+
if (repeatType === "mirror")
|
|
1146
|
+
animation.flipTarget();
|
|
1147
|
+
}
|
|
1148
|
+
isComplete = false;
|
|
1149
|
+
onRepeat && onRepeat();
|
|
1150
|
+
}
|
|
1151
|
+
function complete() {
|
|
1152
|
+
driverControls.stop();
|
|
1153
|
+
onComplete && onComplete();
|
|
1154
|
+
}
|
|
1155
|
+
function update(delta) {
|
|
1156
|
+
if (!isForwardPlayback)
|
|
1157
|
+
delta = -delta;
|
|
1158
|
+
elapsed += delta;
|
|
1159
|
+
if (!isComplete) {
|
|
1160
|
+
const state = animation.next(Math.max(0, elapsed));
|
|
1161
|
+
latest = state.value;
|
|
1162
|
+
if (interpolateFromNumber)
|
|
1163
|
+
latest = interpolateFromNumber(latest);
|
|
1164
|
+
isComplete = isForwardPlayback ? state.done : elapsed <= 0;
|
|
1165
|
+
}
|
|
1166
|
+
onUpdate && onUpdate(latest);
|
|
1167
|
+
if (isComplete) {
|
|
1168
|
+
if (repeatCount === 0) {
|
|
1169
|
+
computedDuration =
|
|
1170
|
+
computedDuration !== undefined ? computedDuration : elapsed;
|
|
1171
|
+
}
|
|
1172
|
+
if (repeatCount < repeatMax) {
|
|
1173
|
+
hasRepeatDelayElapsed(elapsed, computedDuration, repeatDelay, isForwardPlayback) && repeat();
|
|
1174
|
+
}
|
|
1175
|
+
else {
|
|
1176
|
+
complete();
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
function play() {
|
|
1181
|
+
onPlay && onPlay();
|
|
1182
|
+
driverControls = driver(update);
|
|
1183
|
+
driverControls.start();
|
|
747
1184
|
}
|
|
1185
|
+
autoplay && play();
|
|
748
1186
|
return {
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
1187
|
+
stop: () => {
|
|
1188
|
+
onStop && onStop();
|
|
1189
|
+
driverControls.stop();
|
|
1190
|
+
},
|
|
1191
|
+
sample: (t) => {
|
|
1192
|
+
return animation.next(Math.max(0, t)).value;
|
|
1193
|
+
},
|
|
753
1194
|
};
|
|
754
1195
|
}
|
|
755
|
-
const hex = {
|
|
756
|
-
test: isColorString("#"),
|
|
757
|
-
parse: parseHex,
|
|
758
|
-
transform: rgba.transform,
|
|
759
|
-
};
|
|
760
1196
|
|
|
761
|
-
const
|
|
762
|
-
test: (v) => isString(v) && v.endsWith(unit) && v.split(" ").length === 1,
|
|
763
|
-
parse: parseFloat,
|
|
764
|
-
transform: (v) => `${v}${unit}`,
|
|
765
|
-
});
|
|
766
|
-
const degrees = createUnitType("deg");
|
|
767
|
-
const percent = createUnitType("%");
|
|
768
|
-
const px = createUnitType("px");
|
|
769
|
-
const vh = createUnitType("vh");
|
|
770
|
-
const vw = createUnitType("vw");
|
|
771
|
-
const progressPercentage = {
|
|
772
|
-
...percent,
|
|
773
|
-
parse: (v) => percent.parse(v) / 100,
|
|
774
|
-
transform: (v) => percent.transform(v * 100),
|
|
775
|
-
};
|
|
1197
|
+
const cubicBezierAsString = ([a, b, c, d]) => `cubic-bezier(${a}, ${b}, ${c}, ${d})`;
|
|
776
1198
|
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
", " +
|
|
788
|
-
sanitize(alpha.transform(alpha$1)) +
|
|
789
|
-
")");
|
|
790
|
-
},
|
|
791
|
-
};
|
|
1199
|
+
function animateStyle(element, valueName, keyframes, { delay = 0, duration, repeat = 0, repeatType = "loop", ease, times, } = {}) {
|
|
1200
|
+
return element.animate({ [valueName]: keyframes, offset: times }, {
|
|
1201
|
+
delay,
|
|
1202
|
+
duration,
|
|
1203
|
+
easing: Array.isArray(ease) ? cubicBezierAsString(ease) : ease,
|
|
1204
|
+
fill: "both",
|
|
1205
|
+
iterations: repeat + 1,
|
|
1206
|
+
direction: repeatType === "reverse" ? "alternate" : "normal",
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
792
1209
|
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
1210
|
+
/**
|
|
1211
|
+
* 10ms is chosen here as it strikes a balance between smooth
|
|
1212
|
+
* results (more than one keyframe per frame at 60fps) and
|
|
1213
|
+
* keyframe quantity.
|
|
1214
|
+
*/
|
|
1215
|
+
const sampleDelta = 10; //ms
|
|
1216
|
+
function createAcceleratedAnimation(value, valueName, { onUpdate, onComplete, ...options }) {
|
|
1217
|
+
let { keyframes, duration = 0.3, elapsed = 0, ease } = options;
|
|
1218
|
+
/**
|
|
1219
|
+
* If this is a spring animation, pre-generate keyframes and
|
|
1220
|
+
* record duration.
|
|
1221
|
+
*
|
|
1222
|
+
* TODO: When introducing support for values beyond opacity it
|
|
1223
|
+
* might be better to use `animate.sample()`
|
|
1224
|
+
*/
|
|
1225
|
+
if (options.type === "spring") {
|
|
1226
|
+
const springAnimation = spring(options);
|
|
1227
|
+
let state = { done: false, value: keyframes[0] };
|
|
1228
|
+
const springKeyframes = [];
|
|
1229
|
+
let t = 0;
|
|
1230
|
+
while (!state.done) {
|
|
1231
|
+
state = springAnimation.next(t);
|
|
1232
|
+
springKeyframes.push(state.value);
|
|
1233
|
+
t += sampleDelta;
|
|
1234
|
+
}
|
|
1235
|
+
keyframes = springKeyframes;
|
|
1236
|
+
duration = t - sampleDelta;
|
|
1237
|
+
ease = "linear";
|
|
1238
|
+
}
|
|
1239
|
+
const animation = animateStyle(value.owner.current, valueName, keyframes, {
|
|
1240
|
+
...options,
|
|
1241
|
+
delay: -elapsed,
|
|
1242
|
+
duration,
|
|
1243
|
+
/**
|
|
1244
|
+
* This function is currently not called if ease is provided
|
|
1245
|
+
* as a function so the cast is safe.
|
|
1246
|
+
*
|
|
1247
|
+
* However it would be possible for a future refinement to port
|
|
1248
|
+
* in easing pregeneration from Motion One for browsers that
|
|
1249
|
+
* support the upcoming `linear()` easing function.
|
|
1250
|
+
*/
|
|
1251
|
+
ease: ease,
|
|
1252
|
+
});
|
|
1253
|
+
/**
|
|
1254
|
+
* Prefer the `onfinish` prop as it's more widely supported than
|
|
1255
|
+
* the `finished` promise.
|
|
1256
|
+
*
|
|
1257
|
+
* Here, we synchronously set the provided MotionValue to the end
|
|
1258
|
+
* keyframe. If we didn't, when the WAAPI animation is finished it would
|
|
1259
|
+
* be removed from the element which would then revert to its old styles.
|
|
1260
|
+
*/
|
|
1261
|
+
animation.onfinish = () => {
|
|
1262
|
+
value.set(keyframes[keyframes.length - 1]);
|
|
1263
|
+
onComplete && onComplete();
|
|
1264
|
+
};
|
|
1265
|
+
/**
|
|
1266
|
+
* Animation interrupt callback.
|
|
1267
|
+
*/
|
|
1268
|
+
return () => {
|
|
1269
|
+
/**
|
|
1270
|
+
* WAAPI doesn't natively have any interruption capabilities.
|
|
1271
|
+
*
|
|
1272
|
+
* Rather than read commited styles back out of the DOM, we can
|
|
1273
|
+
* create a renderless JS animation and sample it twice to calculate
|
|
1274
|
+
* its current value, "previous" value, and therefore allow
|
|
1275
|
+
* Motion to calculate velocity for any subsequent animation.
|
|
1276
|
+
*/
|
|
1277
|
+
const { currentTime } = animation;
|
|
1278
|
+
if (currentTime) {
|
|
1279
|
+
const sampleAnimation = animate$1(options);
|
|
1280
|
+
value.setWithVelocity(sampleAnimation.sample(currentTime - sampleDelta), sampleAnimation.sample(currentTime), sampleDelta);
|
|
801
1281
|
}
|
|
802
|
-
|
|
803
|
-
|
|
1282
|
+
sync.update(() => animation.cancel());
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
/**
|
|
1287
|
+
* Timeout defined in ms
|
|
1288
|
+
*/
|
|
1289
|
+
function delay(callback, timeout) {
|
|
1290
|
+
const start = performance.now();
|
|
1291
|
+
const checkElapsed = ({ timestamp }) => {
|
|
1292
|
+
const elapsed = timestamp - start;
|
|
1293
|
+
if (elapsed >= timeout) {
|
|
1294
|
+
cancelSync.read(checkElapsed);
|
|
1295
|
+
callback(elapsed - timeout);
|
|
804
1296
|
}
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
: v.hasOwnProperty("red")
|
|
810
|
-
? rgba.transform(v)
|
|
811
|
-
: hsla.transform(v);
|
|
812
|
-
},
|
|
813
|
-
};
|
|
1297
|
+
};
|
|
1298
|
+
sync.read(checkElapsed, true);
|
|
1299
|
+
return () => cancelSync.read(checkElapsed);
|
|
1300
|
+
}
|
|
814
1301
|
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
(((_b = v.match(colorRegex)) === null || _b === void 0 ? void 0 : _b.length) || 0) >
|
|
823
|
-
0);
|
|
1302
|
+
function createInstantAnimation({ keyframes, elapsed, onUpdate, onComplete, }) {
|
|
1303
|
+
const setValue = () => {
|
|
1304
|
+
onUpdate && onUpdate(keyframes[keyframes.length - 1]);
|
|
1305
|
+
onComplete && onComplete();
|
|
1306
|
+
return () => { };
|
|
1307
|
+
};
|
|
1308
|
+
return elapsed ? delay(setValue, -elapsed) : setValue();
|
|
824
1309
|
}
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
const colors = v.match(colorRegex);
|
|
832
|
-
if (colors) {
|
|
833
|
-
numColors = colors.length;
|
|
834
|
-
// Strip colors from input so they're not picked up by number regex.
|
|
835
|
-
// There's a better way to combine these regex searches, but its beyond my regex skills
|
|
836
|
-
v = v.replace(colorRegex, colorToken);
|
|
837
|
-
values.push(...colors.map(color.parse));
|
|
1310
|
+
|
|
1311
|
+
function inertia({ keyframes, velocity = 0, min, max, power = 0.8, timeConstant = 750, bounceStiffness = 500, bounceDamping = 10, restDelta = 1, modifyTarget, driver, onUpdate, onComplete, onStop, }) {
|
|
1312
|
+
const origin = keyframes[0];
|
|
1313
|
+
let currentAnimation;
|
|
1314
|
+
function isOutOfBounds(v) {
|
|
1315
|
+
return (min !== undefined && v < min) || (max !== undefined && v > max);
|
|
838
1316
|
}
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
1317
|
+
function findNearestBoundary(v) {
|
|
1318
|
+
if (min === undefined)
|
|
1319
|
+
return max;
|
|
1320
|
+
if (max === undefined)
|
|
1321
|
+
return min;
|
|
1322
|
+
return Math.abs(min - v) < Math.abs(max - v) ? min : max;
|
|
1323
|
+
}
|
|
1324
|
+
function startAnimation(options) {
|
|
1325
|
+
currentAnimation === null || currentAnimation === void 0 ? void 0 : currentAnimation.stop();
|
|
1326
|
+
currentAnimation = animate$1({
|
|
1327
|
+
keyframes: [0, 1],
|
|
1328
|
+
velocity: 0,
|
|
1329
|
+
...options,
|
|
1330
|
+
driver,
|
|
1331
|
+
onUpdate: (v) => {
|
|
1332
|
+
var _a;
|
|
1333
|
+
onUpdate === null || onUpdate === void 0 ? void 0 : onUpdate(v);
|
|
1334
|
+
(_a = options.onUpdate) === null || _a === void 0 ? void 0 : _a.call(options, v);
|
|
1335
|
+
},
|
|
1336
|
+
onComplete,
|
|
1337
|
+
onStop,
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1340
|
+
function startSpring(options) {
|
|
1341
|
+
startAnimation({
|
|
1342
|
+
type: "spring",
|
|
1343
|
+
stiffness: bounceStiffness,
|
|
1344
|
+
damping: bounceDamping,
|
|
1345
|
+
restDelta,
|
|
1346
|
+
...options,
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
if (isOutOfBounds(origin)) {
|
|
1350
|
+
// Start the animation with spring if outside the defined boundaries
|
|
1351
|
+
startSpring({
|
|
1352
|
+
velocity,
|
|
1353
|
+
keyframes: [origin, findNearestBoundary(origin)],
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
else {
|
|
1357
|
+
/**
|
|
1358
|
+
* Or if the value is out of bounds, simulate the inertia movement
|
|
1359
|
+
* with the decay animation.
|
|
1360
|
+
*
|
|
1361
|
+
* Pre-calculate the target so we can detect if it's out-of-bounds.
|
|
1362
|
+
* If it is, we want to check per frame when to switch to a spring
|
|
1363
|
+
* animation
|
|
1364
|
+
*/
|
|
1365
|
+
let target = power * velocity + origin;
|
|
1366
|
+
if (typeof modifyTarget !== "undefined")
|
|
1367
|
+
target = modifyTarget(target);
|
|
1368
|
+
const boundary = findNearestBoundary(target);
|
|
1369
|
+
const heading = boundary === min ? -1 : 1;
|
|
1370
|
+
let prev;
|
|
1371
|
+
let current;
|
|
1372
|
+
const checkBoundary = (v) => {
|
|
1373
|
+
prev = current;
|
|
1374
|
+
current = v;
|
|
1375
|
+
velocity = velocityPerSecond(v - prev, frameData.delta);
|
|
1376
|
+
if ((heading === 1 && v > boundary) ||
|
|
1377
|
+
(heading === -1 && v < boundary)) {
|
|
1378
|
+
startSpring({ keyframes: [v, boundary], velocity });
|
|
1379
|
+
}
|
|
1380
|
+
};
|
|
1381
|
+
startAnimation({
|
|
1382
|
+
type: "decay",
|
|
1383
|
+
keyframes: [origin, 0],
|
|
1384
|
+
velocity,
|
|
1385
|
+
timeConstant,
|
|
1386
|
+
power,
|
|
1387
|
+
restDelta,
|
|
1388
|
+
modifyTarget,
|
|
1389
|
+
onUpdate: isOutOfBounds(target) ? checkBoundary : undefined,
|
|
1390
|
+
});
|
|
844
1391
|
}
|
|
845
|
-
return {
|
|
846
|
-
|
|
847
|
-
function parse(v) {
|
|
848
|
-
return analyseComplexValue(v).values;
|
|
849
|
-
}
|
|
850
|
-
function createTransformer(source) {
|
|
851
|
-
const { values, numColors, tokenised } = analyseComplexValue(source);
|
|
852
|
-
const numValues = values.length;
|
|
853
|
-
return (v) => {
|
|
854
|
-
let output = tokenised;
|
|
855
|
-
for (let i = 0; i < numValues; i++) {
|
|
856
|
-
output = output.replace(i < numColors ? colorToken : numberToken, i < numColors
|
|
857
|
-
? color.transform(v[i])
|
|
858
|
-
: sanitize(v[i]));
|
|
859
|
-
}
|
|
860
|
-
return output;
|
|
1392
|
+
return {
|
|
1393
|
+
stop: () => currentAnimation === null || currentAnimation === void 0 ? void 0 : currentAnimation.stop(),
|
|
861
1394
|
};
|
|
862
1395
|
}
|
|
863
|
-
const convertNumbersToZero = (v) => typeof v === "number" ? 0 : v;
|
|
864
|
-
function getAnimatableNone$1(v) {
|
|
865
|
-
const parsed = parse(v);
|
|
866
|
-
const transformer = createTransformer(v);
|
|
867
|
-
return transformer(parsed.map(convertNumbersToZero));
|
|
868
|
-
}
|
|
869
|
-
const complex = { test, parse, createTransformer, getAnimatableNone: getAnimatableNone$1 };
|
|
870
|
-
|
|
871
|
-
/**
|
|
872
|
-
* Check if a value is animatable. Examples:
|
|
873
|
-
*
|
|
874
|
-
* ✅: 100, "100px", "#fff"
|
|
875
|
-
* ❌: "block", "url(2.jpg)"
|
|
876
|
-
* @param value
|
|
877
|
-
*
|
|
878
|
-
* @internal
|
|
879
|
-
*/
|
|
880
|
-
const isAnimatable = (key, value) => {
|
|
881
|
-
// If the list of keys tat might be non-animatable grows, replace with Set
|
|
882
|
-
if (key === "zIndex")
|
|
883
|
-
return false;
|
|
884
|
-
// If it's a number or a keyframes array, we can animate it. We might at some point
|
|
885
|
-
// need to do a deep isAnimatable check of keyframes, or let Popmotion handle this,
|
|
886
|
-
// but for now lets leave it like this for performance reasons
|
|
887
|
-
if (typeof value === "number" || Array.isArray(value))
|
|
888
|
-
return true;
|
|
889
|
-
if (typeof value === "string" && // It's animatable if we have a string
|
|
890
|
-
complex.test(value) && // And it contains numbers and/or colors
|
|
891
|
-
!value.startsWith("url(") // Unless it starts with "url("
|
|
892
|
-
) {
|
|
893
|
-
return true;
|
|
894
|
-
}
|
|
895
|
-
return false;
|
|
896
|
-
};
|
|
897
|
-
|
|
898
|
-
const isKeyframesTarget = (v) => {
|
|
899
|
-
return Array.isArray(v);
|
|
900
|
-
};
|
|
901
1396
|
|
|
902
1397
|
const underDampedSpring = () => ({
|
|
903
1398
|
type: "spring",
|
|
@@ -905,10 +1400,10 @@
|
|
|
905
1400
|
damping: 25,
|
|
906
1401
|
restSpeed: 10,
|
|
907
1402
|
});
|
|
908
|
-
const criticallyDampedSpring = (
|
|
1403
|
+
const criticallyDampedSpring = (target) => ({
|
|
909
1404
|
type: "spring",
|
|
910
1405
|
stiffness: 550,
|
|
911
|
-
damping:
|
|
1406
|
+
damping: target === 0 ? 2 * Math.sqrt(550) : 30,
|
|
912
1407
|
restSpeed: 10,
|
|
913
1408
|
});
|
|
914
1409
|
const linearTween = () => ({
|
|
@@ -916,11 +1411,10 @@
|
|
|
916
1411
|
ease: "linear",
|
|
917
1412
|
duration: 0.3,
|
|
918
1413
|
});
|
|
919
|
-
const
|
|
1414
|
+
const keyframesTransition = {
|
|
920
1415
|
type: "keyframes",
|
|
921
1416
|
duration: 0.8,
|
|
922
|
-
|
|
923
|
-
});
|
|
1417
|
+
};
|
|
924
1418
|
const defaultTransitions = {
|
|
925
1419
|
x: underDampedSpring,
|
|
926
1420
|
y: underDampedSpring,
|
|
@@ -937,16 +1431,41 @@
|
|
|
937
1431
|
color: linearTween,
|
|
938
1432
|
default: criticallyDampedSpring,
|
|
939
1433
|
};
|
|
940
|
-
const getDefaultTransition = (valueKey,
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
transitionFactory = keyframes$1;
|
|
1434
|
+
const getDefaultTransition = (valueKey, { keyframes }) => {
|
|
1435
|
+
if (keyframes.length > 2) {
|
|
1436
|
+
return keyframesTransition;
|
|
944
1437
|
}
|
|
945
1438
|
else {
|
|
946
|
-
|
|
947
|
-
|
|
1439
|
+
const factory = defaultTransitions[valueKey] || defaultTransitions.default;
|
|
1440
|
+
return factory(keyframes[1]);
|
|
1441
|
+
}
|
|
1442
|
+
};
|
|
1443
|
+
|
|
1444
|
+
/**
|
|
1445
|
+
* Check if a value is animatable. Examples:
|
|
1446
|
+
*
|
|
1447
|
+
* ✅: 100, "100px", "#fff"
|
|
1448
|
+
* ❌: "block", "url(2.jpg)"
|
|
1449
|
+
* @param value
|
|
1450
|
+
*
|
|
1451
|
+
* @internal
|
|
1452
|
+
*/
|
|
1453
|
+
const isAnimatable = (key, value) => {
|
|
1454
|
+
// If the list of keys tat might be non-animatable grows, replace with Set
|
|
1455
|
+
if (key === "zIndex")
|
|
1456
|
+
return false;
|
|
1457
|
+
// If it's a number or a keyframes array, we can animate it. We might at some point
|
|
1458
|
+
// need to do a deep isAnimatable check of keyframes, or let Popmotion handle this,
|
|
1459
|
+
// but for now lets leave it like this for performance reasons
|
|
1460
|
+
if (typeof value === "number" || Array.isArray(value))
|
|
1461
|
+
return true;
|
|
1462
|
+
if (typeof value === "string" && // It's animatable if we have a string
|
|
1463
|
+
complex.test(value) && // And it contains numbers and/or colors
|
|
1464
|
+
!value.startsWith("url(") // Unless it starts with "url("
|
|
1465
|
+
) {
|
|
1466
|
+
return true;
|
|
948
1467
|
}
|
|
949
|
-
return
|
|
1468
|
+
return false;
|
|
950
1469
|
};
|
|
951
1470
|
|
|
952
1471
|
/**
|
|
@@ -1042,975 +1561,585 @@
|
|
|
1042
1561
|
// Misc
|
|
1043
1562
|
zIndex: int,
|
|
1044
1563
|
// SVG
|
|
1045
|
-
fillOpacity: alpha,
|
|
1046
|
-
strokeOpacity: alpha,
|
|
1047
|
-
numOctaves: int,
|
|
1048
|
-
};
|
|
1049
|
-
|
|
1050
|
-
/**
|
|
1051
|
-
* A map of default value types for common values
|
|
1052
|
-
*/
|
|
1053
|
-
const defaultValueTypes = {
|
|
1054
|
-
...numberValueTypes,
|
|
1055
|
-
// Color props
|
|
1056
|
-
color,
|
|
1057
|
-
backgroundColor: color,
|
|
1058
|
-
outlineColor: color,
|
|
1059
|
-
fill: color,
|
|
1060
|
-
stroke: color,
|
|
1061
|
-
// Border props
|
|
1062
|
-
borderColor: color,
|
|
1063
|
-
borderTopColor: color,
|
|
1064
|
-
borderRightColor: color,
|
|
1065
|
-
borderBottomColor: color,
|
|
1066
|
-
borderLeftColor: color,
|
|
1067
|
-
filter,
|
|
1068
|
-
WebkitFilter: filter,
|
|
1069
|
-
};
|
|
1070
|
-
/**
|
|
1071
|
-
* Gets the default ValueType for the provided value key
|
|
1072
|
-
*/
|
|
1073
|
-
const getDefaultValueType = (key) => defaultValueTypes[key];
|
|
1074
|
-
|
|
1075
|
-
function getAnimatableNone(key, value) {
|
|
1076
|
-
var _a;
|
|
1077
|
-
let defaultValueType = getDefaultValueType(key);
|
|
1078
|
-
if (defaultValueType !== filter)
|
|
1079
|
-
defaultValueType = complex;
|
|
1080
|
-
// If value is not recognised as animatable, ie "none", create an animatable version origin based on the target
|
|
1081
|
-
return (_a = defaultValueType.getAnimatableNone) === null || _a === void 0 ? void 0 : _a.call(defaultValueType, value);
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
const instantAnimationState = {
|
|
1085
|
-
current: false,
|
|
1086
|
-
};
|
|
1087
|
-
|
|
1088
|
-
const isCustomValue = (v) => {
|
|
1089
|
-
return Boolean(v && typeof v === "object" && v.mix && v.toValue);
|
|
1090
|
-
};
|
|
1091
|
-
const resolveFinalValueInKeyframes = (v) => {
|
|
1092
|
-
// TODO maybe throw if v.length - 1 is placeholder token?
|
|
1093
|
-
return isKeyframesTarget(v) ? v[v.length - 1] || 0 : v;
|
|
1094
|
-
};
|
|
1095
|
-
|
|
1096
|
-
/*
|
|
1097
|
-
Value in range from progress
|
|
1098
|
-
|
|
1099
|
-
Given a lower limit and an upper limit, we return the value within
|
|
1100
|
-
that range as expressed by progress (usually a number from 0 to 1)
|
|
1101
|
-
|
|
1102
|
-
So progress = 0.5 would change
|
|
1103
|
-
|
|
1104
|
-
from -------- to
|
|
1105
|
-
|
|
1106
|
-
to
|
|
1107
|
-
|
|
1108
|
-
from ---- to
|
|
1109
|
-
|
|
1110
|
-
E.g. from = 10, to = 20, progress = 0.5 => 15
|
|
1111
|
-
|
|
1112
|
-
@param [number]: Lower limit of range
|
|
1113
|
-
@param [number]: Upper limit of range
|
|
1114
|
-
@param [number]: The progress between lower and upper limits expressed 0-1
|
|
1115
|
-
@return [number]: Value as calculated from progress within range (not limited within range)
|
|
1116
|
-
*/
|
|
1117
|
-
const mix = (from, to, progress) => -progress * from + progress * to + from;
|
|
1118
|
-
|
|
1119
|
-
// Adapted from https://gist.github.com/mjackson/5311256
|
|
1120
|
-
function hueToRgb(p, q, t) {
|
|
1121
|
-
if (t < 0)
|
|
1122
|
-
t += 1;
|
|
1123
|
-
if (t > 1)
|
|
1124
|
-
t -= 1;
|
|
1125
|
-
if (t < 1 / 6)
|
|
1126
|
-
return p + (q - p) * 6 * t;
|
|
1127
|
-
if (t < 1 / 2)
|
|
1128
|
-
return q;
|
|
1129
|
-
if (t < 2 / 3)
|
|
1130
|
-
return p + (q - p) * (2 / 3 - t) * 6;
|
|
1131
|
-
return p;
|
|
1132
|
-
}
|
|
1133
|
-
function hslaToRgba({ hue, saturation, lightness, alpha }) {
|
|
1134
|
-
hue /= 360;
|
|
1135
|
-
saturation /= 100;
|
|
1136
|
-
lightness /= 100;
|
|
1137
|
-
let red = 0;
|
|
1138
|
-
let green = 0;
|
|
1139
|
-
let blue = 0;
|
|
1140
|
-
if (!saturation) {
|
|
1141
|
-
red = green = blue = lightness;
|
|
1142
|
-
}
|
|
1143
|
-
else {
|
|
1144
|
-
const q = lightness < 0.5
|
|
1145
|
-
? lightness * (1 + saturation)
|
|
1146
|
-
: lightness + saturation - lightness * saturation;
|
|
1147
|
-
const p = 2 * lightness - q;
|
|
1148
|
-
red = hueToRgb(p, q, hue + 1 / 3);
|
|
1149
|
-
green = hueToRgb(p, q, hue);
|
|
1150
|
-
blue = hueToRgb(p, q, hue - 1 / 3);
|
|
1151
|
-
}
|
|
1152
|
-
return {
|
|
1153
|
-
red: Math.round(red * 255),
|
|
1154
|
-
green: Math.round(green * 255),
|
|
1155
|
-
blue: Math.round(blue * 255),
|
|
1156
|
-
alpha,
|
|
1157
|
-
};
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
// Linear color space blending
|
|
1161
|
-
// Explained https://www.youtube.com/watch?v=LKnqECcg6Gw
|
|
1162
|
-
// Demonstrated http://codepen.io/osublake/pen/xGVVaN
|
|
1163
|
-
const mixLinearColor = (from, to, v) => {
|
|
1164
|
-
const fromExpo = from * from;
|
|
1165
|
-
return Math.sqrt(Math.max(0, v * (to * to - fromExpo) + fromExpo));
|
|
1166
|
-
};
|
|
1167
|
-
const colorTypes = [hex, rgba, hsla];
|
|
1168
|
-
const getColorType = (v) => colorTypes.find((type) => type.test(v));
|
|
1169
|
-
function asRGBA(color) {
|
|
1170
|
-
const type = getColorType(color);
|
|
1171
|
-
invariant(Boolean(type), `'${color}' is not an animatable color. Use the equivalent color code instead.`);
|
|
1172
|
-
let model = type.parse(color);
|
|
1173
|
-
if (type === hsla) {
|
|
1174
|
-
// TODO Remove this cast - needed since Framer Motion's stricter typing
|
|
1175
|
-
model = hslaToRgba(model);
|
|
1176
|
-
}
|
|
1177
|
-
return model;
|
|
1178
|
-
}
|
|
1179
|
-
const mixColor = (from, to) => {
|
|
1180
|
-
const fromRGBA = asRGBA(from);
|
|
1181
|
-
const toRGBA = asRGBA(to);
|
|
1182
|
-
const blended = { ...fromRGBA };
|
|
1183
|
-
return (v) => {
|
|
1184
|
-
blended.red = mixLinearColor(fromRGBA.red, toRGBA.red, v);
|
|
1185
|
-
blended.green = mixLinearColor(fromRGBA.green, toRGBA.green, v);
|
|
1186
|
-
blended.blue = mixLinearColor(fromRGBA.blue, toRGBA.blue, v);
|
|
1187
|
-
blended.alpha = mix(fromRGBA.alpha, toRGBA.alpha, v);
|
|
1188
|
-
return rgba.transform(blended);
|
|
1189
|
-
};
|
|
1564
|
+
fillOpacity: alpha,
|
|
1565
|
+
strokeOpacity: alpha,
|
|
1566
|
+
numOctaves: int,
|
|
1190
1567
|
};
|
|
1191
1568
|
|
|
1192
1569
|
/**
|
|
1193
|
-
*
|
|
1194
|
-
* Compose other transformers to run linearily
|
|
1195
|
-
* pipe(min(20), max(40))
|
|
1196
|
-
* @param {...functions} transformers
|
|
1197
|
-
* @return {function}
|
|
1570
|
+
* A map of default value types for common values
|
|
1198
1571
|
*/
|
|
1199
|
-
const
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
const numValues = output.length;
|
|
1216
|
-
const blendValue = from.map((fromThis, i) => getMixer(fromThis, to[i]));
|
|
1217
|
-
return (v) => {
|
|
1218
|
-
for (let i = 0; i < numValues; i++) {
|
|
1219
|
-
output[i] = blendValue[i](v);
|
|
1220
|
-
}
|
|
1221
|
-
return output;
|
|
1222
|
-
};
|
|
1223
|
-
};
|
|
1224
|
-
const mixObject = (origin, target) => {
|
|
1225
|
-
const output = { ...origin, ...target };
|
|
1226
|
-
const blendValue = {};
|
|
1227
|
-
for (const key in output) {
|
|
1228
|
-
if (origin[key] !== undefined && target[key] !== undefined) {
|
|
1229
|
-
blendValue[key] = getMixer(origin[key], target[key]);
|
|
1230
|
-
}
|
|
1231
|
-
}
|
|
1232
|
-
return (v) => {
|
|
1233
|
-
for (const key in blendValue) {
|
|
1234
|
-
output[key] = blendValue[key](v);
|
|
1235
|
-
}
|
|
1236
|
-
return output;
|
|
1237
|
-
};
|
|
1238
|
-
};
|
|
1239
|
-
const mixComplex = (origin, target) => {
|
|
1240
|
-
const template = complex.createTransformer(target);
|
|
1241
|
-
const originStats = analyseComplexValue(origin);
|
|
1242
|
-
const targetStats = analyseComplexValue(target);
|
|
1243
|
-
const canInterpolate = originStats.numColors === targetStats.numColors &&
|
|
1244
|
-
originStats.numNumbers >= targetStats.numNumbers;
|
|
1245
|
-
if (canInterpolate) {
|
|
1246
|
-
return pipe(mixArray(originStats.values, targetStats.values), template);
|
|
1247
|
-
}
|
|
1248
|
-
else {
|
|
1249
|
-
warning(true, `Complex values '${origin}' and '${target}' too different to mix. Ensure all colors are of the same type, and that each contains the same quantity of number and color values. Falling back to instant transition.`);
|
|
1250
|
-
return (p) => `${p > 0 ? target : origin}`;
|
|
1251
|
-
}
|
|
1252
|
-
};
|
|
1253
|
-
|
|
1254
|
-
/*
|
|
1255
|
-
Progress within given range
|
|
1256
|
-
|
|
1257
|
-
Given a lower limit and an upper limit, we return the progress
|
|
1258
|
-
(expressed as a number 0-1) represented by the given value, and
|
|
1259
|
-
limit that progress to within 0-1.
|
|
1260
|
-
|
|
1261
|
-
@param [number]: Lower limit
|
|
1262
|
-
@param [number]: Upper limit
|
|
1263
|
-
@param [number]: Value to find progress within given range
|
|
1264
|
-
@return [number]: Progress of value within range as expressed 0-1
|
|
1265
|
-
*/
|
|
1266
|
-
const progress = (from, to, value) => {
|
|
1267
|
-
const toFromDifference = to - from;
|
|
1268
|
-
return toFromDifference === 0 ? 1 : (value - from) / toFromDifference;
|
|
1572
|
+
const defaultValueTypes = {
|
|
1573
|
+
...numberValueTypes,
|
|
1574
|
+
// Color props
|
|
1575
|
+
color,
|
|
1576
|
+
backgroundColor: color,
|
|
1577
|
+
outlineColor: color,
|
|
1578
|
+
fill: color,
|
|
1579
|
+
stroke: color,
|
|
1580
|
+
// Border props
|
|
1581
|
+
borderColor: color,
|
|
1582
|
+
borderTopColor: color,
|
|
1583
|
+
borderRightColor: color,
|
|
1584
|
+
borderBottomColor: color,
|
|
1585
|
+
borderLeftColor: color,
|
|
1586
|
+
filter,
|
|
1587
|
+
WebkitFilter: filter,
|
|
1269
1588
|
};
|
|
1270
|
-
|
|
1271
|
-
const mixNumber = (from, to) => (p) => mix(from, to, p);
|
|
1272
|
-
function detectMixerFactory(v) {
|
|
1273
|
-
if (typeof v === "number") {
|
|
1274
|
-
return mixNumber;
|
|
1275
|
-
}
|
|
1276
|
-
else if (typeof v === "string") {
|
|
1277
|
-
if (color.test(v)) {
|
|
1278
|
-
return mixColor;
|
|
1279
|
-
}
|
|
1280
|
-
else {
|
|
1281
|
-
return mixComplex;
|
|
1282
|
-
}
|
|
1283
|
-
}
|
|
1284
|
-
else if (Array.isArray(v)) {
|
|
1285
|
-
return mixArray;
|
|
1286
|
-
}
|
|
1287
|
-
else if (typeof v === "object") {
|
|
1288
|
-
return mixObject;
|
|
1289
|
-
}
|
|
1290
|
-
return mixNumber;
|
|
1291
|
-
}
|
|
1292
|
-
function createMixers(output, ease, customMixer) {
|
|
1293
|
-
const mixers = [];
|
|
1294
|
-
const mixerFactory = customMixer || detectMixerFactory(output[0]);
|
|
1295
|
-
const numMixers = output.length - 1;
|
|
1296
|
-
for (let i = 0; i < numMixers; i++) {
|
|
1297
|
-
let mixer = mixerFactory(output[i], output[i + 1]);
|
|
1298
|
-
if (ease) {
|
|
1299
|
-
const easingFunction = Array.isArray(ease) ? ease[i] : ease;
|
|
1300
|
-
mixer = pipe(easingFunction, mixer);
|
|
1301
|
-
}
|
|
1302
|
-
mixers.push(mixer);
|
|
1303
|
-
}
|
|
1304
|
-
return mixers;
|
|
1305
|
-
}
|
|
1306
1589
|
/**
|
|
1307
|
-
*
|
|
1308
|
-
*
|
|
1309
|
-
* Accepts:
|
|
1310
|
-
* - Numbers
|
|
1311
|
-
* - Colors (hex, hsl, hsla, rgb, rgba)
|
|
1312
|
-
* - Complex (combinations of one or more numbers or strings)
|
|
1313
|
-
*
|
|
1314
|
-
* ```jsx
|
|
1315
|
-
* const mixColor = interpolate([0, 1], ['#fff', '#000'])
|
|
1316
|
-
*
|
|
1317
|
-
* mixColor(0.5) // 'rgba(128, 128, 128, 1)'
|
|
1318
|
-
* ```
|
|
1319
|
-
*
|
|
1320
|
-
* TODO Revist this approach once we've moved to data models for values,
|
|
1321
|
-
* probably not needed to pregenerate mixer functions.
|
|
1322
|
-
*
|
|
1323
|
-
* @public
|
|
1590
|
+
* Gets the default ValueType for the provided value key
|
|
1324
1591
|
*/
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
if (
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
const mixers = createMixers(output, ease, mixer);
|
|
1335
|
-
const numMixers = mixers.length;
|
|
1336
|
-
const interpolator = (v) => {
|
|
1337
|
-
let i = 0;
|
|
1338
|
-
if (numMixers > 1) {
|
|
1339
|
-
for (; i < input.length - 2; i++) {
|
|
1340
|
-
if (v < input[i + 1])
|
|
1341
|
-
break;
|
|
1342
|
-
}
|
|
1343
|
-
}
|
|
1344
|
-
const progressInRange = progress(input[i], input[i + 1], v);
|
|
1345
|
-
return mixers[i](progressInRange);
|
|
1346
|
-
};
|
|
1347
|
-
return isClamp
|
|
1348
|
-
? (v) => interpolator(clamp(input[0], input[inputLength - 1], v))
|
|
1349
|
-
: interpolator;
|
|
1592
|
+
const getDefaultValueType = (key) => defaultValueTypes[key];
|
|
1593
|
+
|
|
1594
|
+
function getAnimatableNone(key, value) {
|
|
1595
|
+
var _a;
|
|
1596
|
+
let defaultValueType = getDefaultValueType(key);
|
|
1597
|
+
if (defaultValueType !== filter)
|
|
1598
|
+
defaultValueType = complex;
|
|
1599
|
+
// If value is not recognised as animatable, ie "none", create an animatable version origin based on the target
|
|
1600
|
+
return (_a = defaultValueType.getAnimatableNone) === null || _a === void 0 ? void 0 : _a.call(defaultValueType, value);
|
|
1350
1601
|
}
|
|
1351
1602
|
|
|
1352
|
-
|
|
1353
|
-
|
|
1603
|
+
/**
|
|
1604
|
+
* Decide whether a transition is defined on a given Transition.
|
|
1605
|
+
* This filters out orchestration options and returns true
|
|
1606
|
+
* if any options are left.
|
|
1607
|
+
*/
|
|
1608
|
+
function isTransitionDefined({ when, delay: _delay, delayChildren, staggerChildren, staggerDirection, repeat, repeatType, repeatDelay, from, ...transition }) {
|
|
1609
|
+
return !!Object.keys(transition).length;
|
|
1354
1610
|
}
|
|
1355
|
-
function
|
|
1356
|
-
|
|
1357
|
-
|
|
1611
|
+
function isZero(value) {
|
|
1612
|
+
return (value === 0 ||
|
|
1613
|
+
(typeof value === "string" &&
|
|
1614
|
+
parseFloat(value) === 0 &&
|
|
1615
|
+
value.indexOf(" ") === -1));
|
|
1358
1616
|
}
|
|
1359
|
-
function
|
|
1360
|
-
return
|
|
1617
|
+
function getZeroUnit(potentialUnitType) {
|
|
1618
|
+
return typeof potentialUnitType === "number"
|
|
1619
|
+
? 0
|
|
1620
|
+
: getAnimatableNone("", potentialUnitType);
|
|
1361
1621
|
}
|
|
1362
|
-
function
|
|
1363
|
-
|
|
1364
|
-
* This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
|
|
1365
|
-
* to reduce GC during animation.
|
|
1366
|
-
*/
|
|
1367
|
-
const state = { done: false, value: from };
|
|
1368
|
-
/**
|
|
1369
|
-
* Convert values to an array if they've been given as from/to options
|
|
1370
|
-
*/
|
|
1371
|
-
const values = Array.isArray(to) ? to : [from, to];
|
|
1372
|
-
/**
|
|
1373
|
-
* Create a times array based on the provided 0-1 offsets
|
|
1374
|
-
*/
|
|
1375
|
-
const times = convertOffsetToTimes(
|
|
1376
|
-
// Only use the provided offsets if they're the correct length
|
|
1377
|
-
// TODO Maybe we should warn here if there's a length mismatch
|
|
1378
|
-
offset && offset.length === values.length
|
|
1379
|
-
? offset
|
|
1380
|
-
: defaultOffset(values), duration);
|
|
1381
|
-
function createInterpolator() {
|
|
1382
|
-
return interpolate(times, values, {
|
|
1383
|
-
ease: Array.isArray(ease) ? ease : defaultEasing(values, ease),
|
|
1384
|
-
});
|
|
1385
|
-
}
|
|
1386
|
-
let interpolator = createInterpolator();
|
|
1387
|
-
return {
|
|
1388
|
-
next: (t) => {
|
|
1389
|
-
state.value = interpolator(t);
|
|
1390
|
-
state.done = t >= duration;
|
|
1391
|
-
return state;
|
|
1392
|
-
},
|
|
1393
|
-
flipTarget: () => {
|
|
1394
|
-
values.reverse();
|
|
1395
|
-
interpolator = createInterpolator();
|
|
1396
|
-
},
|
|
1397
|
-
};
|
|
1622
|
+
function getValueTransition(transition, key) {
|
|
1623
|
+
return transition[key] || transition["default"] || transition;
|
|
1398
1624
|
}
|
|
1399
1625
|
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1626
|
+
function getKeyframes(value, valueName, target, transition) {
|
|
1627
|
+
const isTargetAnimatable = isAnimatable(valueName, target);
|
|
1628
|
+
let origin = transition.from !== undefined ? transition.from : value.get();
|
|
1629
|
+
if (origin === "none" && isTargetAnimatable && typeof target === "string") {
|
|
1630
|
+
/**
|
|
1631
|
+
* If we're trying to animate from "none", try and get an animatable version
|
|
1632
|
+
* of the target. This could be improved to work both ways.
|
|
1633
|
+
*/
|
|
1634
|
+
origin = getAnimatableNone(valueName, target);
|
|
1635
|
+
}
|
|
1636
|
+
else if (isZero(origin) && typeof target === "string") {
|
|
1637
|
+
origin = getZeroUnit(target);
|
|
1638
|
+
}
|
|
1639
|
+
else if (!Array.isArray(target) &&
|
|
1640
|
+
isZero(target) &&
|
|
1641
|
+
typeof origin === "string") {
|
|
1642
|
+
target = getZeroUnit(origin);
|
|
1643
|
+
}
|
|
1410
1644
|
/**
|
|
1411
|
-
*
|
|
1645
|
+
* If the target has been defined as a series of keyframes
|
|
1412
1646
|
*/
|
|
1413
|
-
|
|
1414
|
-
duration = clamp(minDuration, maxDuration, duration / 1000);
|
|
1415
|
-
if (dampingRatio < 1) {
|
|
1416
|
-
/**
|
|
1417
|
-
* Underdamped spring
|
|
1418
|
-
*/
|
|
1419
|
-
envelope = (undampedFreq) => {
|
|
1420
|
-
const exponentialDecay = undampedFreq * dampingRatio;
|
|
1421
|
-
const delta = exponentialDecay * duration;
|
|
1422
|
-
const a = exponentialDecay - velocity;
|
|
1423
|
-
const b = calcAngularFreq(undampedFreq, dampingRatio);
|
|
1424
|
-
const c = Math.exp(-delta);
|
|
1425
|
-
return safeMin - (a / b) * c;
|
|
1426
|
-
};
|
|
1427
|
-
derivative = (undampedFreq) => {
|
|
1428
|
-
const exponentialDecay = undampedFreq * dampingRatio;
|
|
1429
|
-
const delta = exponentialDecay * duration;
|
|
1430
|
-
const d = delta * velocity + velocity;
|
|
1431
|
-
const e = Math.pow(dampingRatio, 2) * Math.pow(undampedFreq, 2) * duration;
|
|
1432
|
-
const f = Math.exp(-delta);
|
|
1433
|
-
const g = calcAngularFreq(Math.pow(undampedFreq, 2), dampingRatio);
|
|
1434
|
-
const factor = -envelope(undampedFreq) + safeMin > 0 ? -1 : 1;
|
|
1435
|
-
return (factor * ((d - e) * f)) / g;
|
|
1436
|
-
};
|
|
1437
|
-
}
|
|
1438
|
-
else {
|
|
1647
|
+
if (Array.isArray(target)) {
|
|
1439
1648
|
/**
|
|
1440
|
-
*
|
|
1649
|
+
* Ensure an initial wildcard keyframe is hydrated by the origin.
|
|
1650
|
+
* TODO: Support extra wildcard keyframes i.e [1, null, 0]
|
|
1441
1651
|
*/
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
};
|
|
1447
|
-
derivative = (undampedFreq) => {
|
|
1448
|
-
const a = Math.exp(-undampedFreq * duration);
|
|
1449
|
-
const b = (velocity - undampedFreq) * (duration * duration);
|
|
1450
|
-
return a * b;
|
|
1451
|
-
};
|
|
1452
|
-
}
|
|
1453
|
-
const initialGuess = 5 / duration;
|
|
1454
|
-
const undampedFreq = approximateRoot(envelope, derivative, initialGuess);
|
|
1455
|
-
duration = duration * 1000;
|
|
1456
|
-
if (isNaN(undampedFreq)) {
|
|
1457
|
-
return {
|
|
1458
|
-
stiffness: 100,
|
|
1459
|
-
damping: 10,
|
|
1460
|
-
duration,
|
|
1461
|
-
};
|
|
1652
|
+
if (target[0] === null) {
|
|
1653
|
+
target[0] = origin;
|
|
1654
|
+
}
|
|
1655
|
+
return target;
|
|
1462
1656
|
}
|
|
1463
1657
|
else {
|
|
1464
|
-
|
|
1465
|
-
return {
|
|
1466
|
-
stiffness,
|
|
1467
|
-
damping: dampingRatio * 2 * Math.sqrt(mass * stiffness),
|
|
1468
|
-
duration,
|
|
1469
|
-
};
|
|
1658
|
+
return [origin, target];
|
|
1470
1659
|
}
|
|
1471
1660
|
}
|
|
1472
|
-
const rootIterations = 12;
|
|
1473
|
-
function approximateRoot(envelope, derivative, initialGuess) {
|
|
1474
|
-
let result = initialGuess;
|
|
1475
|
-
for (let i = 1; i < rootIterations; i++) {
|
|
1476
|
-
result = result - envelope(result) / derivative(result);
|
|
1477
|
-
}
|
|
1478
|
-
return result;
|
|
1479
|
-
}
|
|
1480
|
-
function calcAngularFreq(undampedFreq, dampingRatio) {
|
|
1481
|
-
return undampedFreq * Math.sqrt(1 - dampingRatio * dampingRatio);
|
|
1482
|
-
}
|
|
1483
1661
|
|
|
1484
|
-
const
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
}
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1662
|
+
const featureTests = {
|
|
1663
|
+
waapi: () => Object.hasOwnProperty.call(Element.prototype, "animate"),
|
|
1664
|
+
};
|
|
1665
|
+
const results = {};
|
|
1666
|
+
const supports = {};
|
|
1667
|
+
/**
|
|
1668
|
+
* Generate features tests that cache their results.
|
|
1669
|
+
*/
|
|
1670
|
+
for (const key in featureTests) {
|
|
1671
|
+
supports[key] = () => {
|
|
1672
|
+
if (results[key] === undefined)
|
|
1673
|
+
results[key] = featureTests[key]();
|
|
1674
|
+
return results[key];
|
|
1497
1675
|
};
|
|
1498
|
-
// stiffness/damping/mass overrides duration/bounce
|
|
1499
|
-
if (!isSpringType(options, physicsKeys) &&
|
|
1500
|
-
isSpringType(options, durationKeys)) {
|
|
1501
|
-
const derived = findSpring(options);
|
|
1502
|
-
springOptions = {
|
|
1503
|
-
...springOptions,
|
|
1504
|
-
...derived,
|
|
1505
|
-
velocity: 0.0,
|
|
1506
|
-
mass: 1.0,
|
|
1507
|
-
};
|
|
1508
|
-
springOptions.isResolvedFromDuration = true;
|
|
1509
|
-
}
|
|
1510
|
-
return springOptions;
|
|
1511
1676
|
}
|
|
1512
|
-
|
|
1677
|
+
|
|
1513
1678
|
/**
|
|
1514
|
-
*
|
|
1679
|
+
* A list of values that can be hardware-accelerated.
|
|
1515
1680
|
*/
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
*/
|
|
1521
|
-
const state = { done: false, value: from };
|
|
1522
|
-
let { stiffness, damping, mass, velocity, duration, isResolvedFromDuration, } = getSpringOptions(options);
|
|
1523
|
-
let resolveSpring = zero;
|
|
1524
|
-
let initialVelocity = velocity ? -(velocity / 1000) : 0.0;
|
|
1525
|
-
const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass));
|
|
1526
|
-
function createSpring() {
|
|
1527
|
-
const initialDelta = to - from;
|
|
1528
|
-
const undampedAngularFreq = Math.sqrt(stiffness / mass) / 1000;
|
|
1681
|
+
const acceleratedValues = new Set(["opacity"]);
|
|
1682
|
+
const createMotionValueAnimation = (valueName, value, target, transition = {}) => {
|
|
1683
|
+
return (onComplete) => {
|
|
1684
|
+
const valueTransition = getValueTransition(transition, valueName) || {};
|
|
1529
1685
|
/**
|
|
1530
|
-
*
|
|
1531
|
-
* to
|
|
1686
|
+
* Most transition values are currently completely overwritten by value-specific
|
|
1687
|
+
* transitions. In the future it'd be nicer to blend these transitions. But for now
|
|
1688
|
+
* delay actually does inherit from the root transition if not value-specific.
|
|
1532
1689
|
*/
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1690
|
+
const delay = valueTransition.delay || transition.delay || 0;
|
|
1691
|
+
/**
|
|
1692
|
+
* Elapsed isn't a public transition option but can be passed through from
|
|
1693
|
+
* optimized appear effects in milliseconds.
|
|
1694
|
+
*/
|
|
1695
|
+
let { elapsed = 0 } = transition;
|
|
1696
|
+
elapsed = elapsed - secondsToMilliseconds(delay);
|
|
1697
|
+
const keyframes = getKeyframes(value, valueName, target, valueTransition);
|
|
1698
|
+
/**
|
|
1699
|
+
* Check if we're able to animate between the start and end keyframes,
|
|
1700
|
+
* and throw a warning if we're attempting to animate between one that's
|
|
1701
|
+
* animatable and another that isn't.
|
|
1702
|
+
*/
|
|
1703
|
+
const originKeyframe = keyframes[0];
|
|
1704
|
+
const targetKeyframe = keyframes[keyframes.length - 1];
|
|
1705
|
+
const isOriginAnimatable = isAnimatable(valueName, originKeyframe);
|
|
1706
|
+
const isTargetAnimatable = isAnimatable(valueName, targetKeyframe);
|
|
1707
|
+
warning(isOriginAnimatable === isTargetAnimatable, `You are trying to animate ${valueName} from "${originKeyframe}" to "${targetKeyframe}". ${originKeyframe} is not an animatable value - to enable this animation set ${originKeyframe} to a value animatable to ${targetKeyframe} via the \`style\` property.`);
|
|
1708
|
+
let options = {
|
|
1709
|
+
keyframes,
|
|
1710
|
+
velocity: value.getVelocity(),
|
|
1711
|
+
...valueTransition,
|
|
1712
|
+
elapsed,
|
|
1713
|
+
onUpdate: (v) => {
|
|
1714
|
+
value.set(v);
|
|
1715
|
+
valueTransition.onUpdate && valueTransition.onUpdate(v);
|
|
1716
|
+
},
|
|
1717
|
+
onComplete: () => {
|
|
1718
|
+
onComplete();
|
|
1719
|
+
valueTransition.onComplete && valueTransition.onComplete();
|
|
1720
|
+
},
|
|
1721
|
+
};
|
|
1722
|
+
if (!isOriginAnimatable ||
|
|
1723
|
+
!isTargetAnimatable ||
|
|
1724
|
+
instantAnimationState.current ||
|
|
1725
|
+
valueTransition.type === false) {
|
|
1726
|
+
/**
|
|
1727
|
+
* If we can't animate this value, or the global instant animation flag is set,
|
|
1728
|
+
* or this is simply defined as an instant transition, return an instant transition.
|
|
1729
|
+
*/
|
|
1730
|
+
return createInstantAnimation(options);
|
|
1557
1731
|
}
|
|
1558
|
-
else {
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
return (to -
|
|
1566
|
-
(envelope *
|
|
1567
|
-
((initialVelocity +
|
|
1568
|
-
dampingRatio * undampedAngularFreq * initialDelta) *
|
|
1569
|
-
Math.sinh(freqForT) +
|
|
1570
|
-
dampedAngularFreq *
|
|
1571
|
-
initialDelta *
|
|
1572
|
-
Math.cosh(freqForT))) /
|
|
1573
|
-
dampedAngularFreq);
|
|
1574
|
-
};
|
|
1732
|
+
else if (valueTransition.type === "inertia") {
|
|
1733
|
+
/**
|
|
1734
|
+
* If this is an inertia animation, we currently don't support pre-generating
|
|
1735
|
+
* keyframes for this as such it must always run on the main thread.
|
|
1736
|
+
*/
|
|
1737
|
+
const animation = inertia(options);
|
|
1738
|
+
return () => animation.stop();
|
|
1575
1739
|
}
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
};
|
|
1614
|
-
}
|
|
1615
|
-
spring.needsInterpolation = (a, b) => typeof a === "string" || typeof b === "string";
|
|
1616
|
-
const zero = (_t) => 0;
|
|
1617
|
-
|
|
1618
|
-
function decay({ velocity = 0, from = 0, power = 0.8, timeConstant = 350, restDelta = 0.5, modifyTarget, }) {
|
|
1619
|
-
/**
|
|
1620
|
-
* This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
|
|
1621
|
-
* to reduce GC during animation.
|
|
1622
|
-
*/
|
|
1623
|
-
const state = { done: false, value: from };
|
|
1624
|
-
let amplitude = power * velocity;
|
|
1625
|
-
const ideal = from + amplitude;
|
|
1626
|
-
const target = modifyTarget === undefined ? ideal : modifyTarget(ideal);
|
|
1627
|
-
/**
|
|
1628
|
-
* If the target has changed we need to re-calculate the amplitude, otherwise
|
|
1629
|
-
* the animation will start from the wrong position.
|
|
1630
|
-
*/
|
|
1631
|
-
if (target !== ideal)
|
|
1632
|
-
amplitude = target - from;
|
|
1633
|
-
return {
|
|
1634
|
-
next: (t) => {
|
|
1635
|
-
const delta = -amplitude * Math.exp(-t / timeConstant);
|
|
1636
|
-
state.done = !(delta > restDelta || delta < -restDelta);
|
|
1637
|
-
state.value = state.done ? target : target + delta;
|
|
1638
|
-
return state;
|
|
1639
|
-
},
|
|
1640
|
-
flipTarget: () => { },
|
|
1641
|
-
};
|
|
1642
|
-
}
|
|
1643
|
-
|
|
1644
|
-
const types = { decay, keyframes, spring };
|
|
1645
|
-
function loopElapsed(elapsed, duration, delay = 0) {
|
|
1646
|
-
return elapsed - duration - delay;
|
|
1647
|
-
}
|
|
1648
|
-
function reverseElapsed(elapsed, duration = 0, delay = 0, isForwardPlayback = true) {
|
|
1649
|
-
return isForwardPlayback
|
|
1650
|
-
? loopElapsed(duration + -elapsed, duration, delay)
|
|
1651
|
-
: duration - (elapsed - duration) + delay;
|
|
1652
|
-
}
|
|
1653
|
-
function hasRepeatDelayElapsed(elapsed, duration, delay, isForwardPlayback) {
|
|
1654
|
-
return isForwardPlayback ? elapsed >= duration + delay : elapsed <= -delay;
|
|
1655
|
-
}
|
|
1656
|
-
const framesync = (update) => {
|
|
1657
|
-
const passTimestamp = ({ delta }) => update(delta);
|
|
1658
|
-
return {
|
|
1659
|
-
start: () => sync.update(passTimestamp, true),
|
|
1660
|
-
stop: () => cancelSync.update(passTimestamp),
|
|
1661
|
-
};
|
|
1662
|
-
};
|
|
1663
|
-
function animate$1({ from, autoplay = true, driver = framesync, elapsed = 0, repeat: repeatMax = 0, repeatType = "loop", repeatDelay = 0, onPlay, onStop, onComplete, onRepeat, onUpdate, type = "keyframes", ...options }) {
|
|
1664
|
-
var _a, _b;
|
|
1665
|
-
let { to } = options;
|
|
1666
|
-
let driverControls;
|
|
1667
|
-
let repeatCount = 0;
|
|
1668
|
-
let computedDuration = options
|
|
1669
|
-
.duration;
|
|
1670
|
-
let latest;
|
|
1671
|
-
let isComplete = false;
|
|
1672
|
-
let isForwardPlayback = true;
|
|
1673
|
-
let interpolateFromNumber;
|
|
1674
|
-
const animator = types[Array.isArray(to) ? "keyframes" : type];
|
|
1675
|
-
if ((_b = (_a = animator).needsInterpolation) === null || _b === void 0 ? void 0 : _b.call(_a, from, to)) {
|
|
1676
|
-
interpolateFromNumber = interpolate([0, 100], [from, to], {
|
|
1677
|
-
clamp: false,
|
|
1678
|
-
});
|
|
1679
|
-
from = 0;
|
|
1680
|
-
to = 100;
|
|
1681
|
-
}
|
|
1682
|
-
const animation = animator({ ...options, from, to });
|
|
1683
|
-
function repeat() {
|
|
1684
|
-
repeatCount++;
|
|
1685
|
-
if (repeatType === "reverse") {
|
|
1686
|
-
isForwardPlayback = repeatCount % 2 === 0;
|
|
1687
|
-
elapsed = reverseElapsed(elapsed, computedDuration, repeatDelay, isForwardPlayback);
|
|
1740
|
+
/**
|
|
1741
|
+
* If there's no transition defined for this value, we can generate
|
|
1742
|
+
* unqiue transition settings for this value.
|
|
1743
|
+
*/
|
|
1744
|
+
if (!isTransitionDefined(valueTransition)) {
|
|
1745
|
+
options = {
|
|
1746
|
+
...options,
|
|
1747
|
+
...getDefaultTransition(valueName, options),
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
/**
|
|
1751
|
+
* Both WAAPI and our internal animation functions use durations
|
|
1752
|
+
* as defined by milliseconds, while our external API defines them
|
|
1753
|
+
* as seconds.
|
|
1754
|
+
*/
|
|
1755
|
+
if (options.duration) {
|
|
1756
|
+
options.duration = secondsToMilliseconds(options.duration);
|
|
1757
|
+
}
|
|
1758
|
+
if (options.repeatDelay) {
|
|
1759
|
+
options.repeatDelay = secondsToMilliseconds(options.repeatDelay);
|
|
1760
|
+
}
|
|
1761
|
+
const visualElement = value.owner;
|
|
1762
|
+
const element = visualElement && visualElement.current;
|
|
1763
|
+
const canAccelerateAnimation = supports.waapi() &&
|
|
1764
|
+
acceleratedValues.has(valueName) &&
|
|
1765
|
+
!options.repeatDelay &&
|
|
1766
|
+
options.repeatType !== "mirror" &&
|
|
1767
|
+
options.damping !== 0 &&
|
|
1768
|
+
typeof options.ease !== "function" &&
|
|
1769
|
+
visualElement &&
|
|
1770
|
+
element instanceof HTMLElement &&
|
|
1771
|
+
!visualElement.getProps().onUpdate;
|
|
1772
|
+
if (canAccelerateAnimation) {
|
|
1773
|
+
/**
|
|
1774
|
+
* If this animation is capable of being run via WAAPI, then do so.
|
|
1775
|
+
*/
|
|
1776
|
+
return createAcceleratedAnimation(value, valueName, options);
|
|
1688
1777
|
}
|
|
1689
1778
|
else {
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1779
|
+
/**
|
|
1780
|
+
* Otherwise, fall back to the main thread.
|
|
1781
|
+
*/
|
|
1782
|
+
const animation = animate$1(options);
|
|
1783
|
+
return () => animation.stop();
|
|
1693
1784
|
}
|
|
1694
|
-
|
|
1695
|
-
|
|
1785
|
+
};
|
|
1786
|
+
};
|
|
1787
|
+
|
|
1788
|
+
function addUniqueItem(arr, item) {
|
|
1789
|
+
if (arr.indexOf(item) === -1)
|
|
1790
|
+
arr.push(item);
|
|
1791
|
+
}
|
|
1792
|
+
function removeItem(arr, item) {
|
|
1793
|
+
const index = arr.indexOf(item);
|
|
1794
|
+
if (index > -1)
|
|
1795
|
+
arr.splice(index, 1);
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
class SubscriptionManager {
|
|
1799
|
+
constructor() {
|
|
1800
|
+
this.subscriptions = [];
|
|
1696
1801
|
}
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1802
|
+
add(handler) {
|
|
1803
|
+
addUniqueItem(this.subscriptions, handler);
|
|
1804
|
+
return () => removeItem(this.subscriptions, handler);
|
|
1700
1805
|
}
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
if (
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
isComplete = isForwardPlayback ? state.done : elapsed <= 0;
|
|
1806
|
+
notify(a, b, c) {
|
|
1807
|
+
const numSubscriptions = this.subscriptions.length;
|
|
1808
|
+
if (!numSubscriptions)
|
|
1809
|
+
return;
|
|
1810
|
+
if (numSubscriptions === 1) {
|
|
1811
|
+
/**
|
|
1812
|
+
* If there's only a single handler we can just call it without invoking a loop.
|
|
1813
|
+
*/
|
|
1814
|
+
this.subscriptions[0](a, b, c);
|
|
1711
1815
|
}
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
}
|
|
1721
|
-
else {
|
|
1722
|
-
complete();
|
|
1816
|
+
else {
|
|
1817
|
+
for (let i = 0; i < numSubscriptions; i++) {
|
|
1818
|
+
/**
|
|
1819
|
+
* Check whether the handler exists before firing as it's possible
|
|
1820
|
+
* the subscriptions were modified during this loop running.
|
|
1821
|
+
*/
|
|
1822
|
+
const handler = this.subscriptions[i];
|
|
1823
|
+
handler && handler(a, b, c);
|
|
1723
1824
|
}
|
|
1724
1825
|
}
|
|
1725
1826
|
}
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1827
|
+
getSize() {
|
|
1828
|
+
return this.subscriptions.length;
|
|
1829
|
+
}
|
|
1830
|
+
clear() {
|
|
1831
|
+
this.subscriptions.length = 0;
|
|
1730
1832
|
}
|
|
1731
|
-
autoplay && play();
|
|
1732
|
-
return {
|
|
1733
|
-
stop: () => {
|
|
1734
|
-
onStop && onStop();
|
|
1735
|
-
driverControls.stop();
|
|
1736
|
-
},
|
|
1737
|
-
};
|
|
1738
1833
|
}
|
|
1739
1834
|
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1835
|
+
const isFloat = (value) => {
|
|
1836
|
+
return !isNaN(parseFloat(value));
|
|
1837
|
+
};
|
|
1838
|
+
/**
|
|
1839
|
+
* `MotionValue` is used to track the state and velocity of motion values.
|
|
1840
|
+
*
|
|
1841
|
+
* @public
|
|
1842
|
+
*/
|
|
1843
|
+
class MotionValue {
|
|
1844
|
+
/**
|
|
1845
|
+
* @param init - The initiating value
|
|
1846
|
+
* @param config - Optional configuration options
|
|
1847
|
+
*
|
|
1848
|
+
* - `transformer`: A function to transform incoming values with.
|
|
1849
|
+
*
|
|
1850
|
+
* @internal
|
|
1851
|
+
*/
|
|
1852
|
+
constructor(init, options = {}) {
|
|
1853
|
+
/**
|
|
1854
|
+
* This will be replaced by the build step with the latest version number.
|
|
1855
|
+
* When MotionValues are provided to motion components, warn if versions are mixed.
|
|
1856
|
+
*/
|
|
1857
|
+
this.version = "7.9.0";
|
|
1858
|
+
/**
|
|
1859
|
+
* Duration, in milliseconds, since last updating frame.
|
|
1860
|
+
*
|
|
1861
|
+
* @internal
|
|
1862
|
+
*/
|
|
1863
|
+
this.timeDelta = 0;
|
|
1864
|
+
/**
|
|
1865
|
+
* Timestamp of the last time this `MotionValue` was updated.
|
|
1866
|
+
*
|
|
1867
|
+
* @internal
|
|
1868
|
+
*/
|
|
1869
|
+
this.lastUpdated = 0;
|
|
1870
|
+
/**
|
|
1871
|
+
* Functions to notify when the `MotionValue` updates.
|
|
1872
|
+
*
|
|
1873
|
+
* @internal
|
|
1874
|
+
*/
|
|
1875
|
+
this.updateSubscribers = new SubscriptionManager();
|
|
1876
|
+
/**
|
|
1877
|
+
* Functions to notify when the velocity updates.
|
|
1878
|
+
*
|
|
1879
|
+
* @internal
|
|
1880
|
+
*/
|
|
1881
|
+
this.velocityUpdateSubscribers = new SubscriptionManager();
|
|
1882
|
+
/**
|
|
1883
|
+
* Functions to notify when the `MotionValue` updates and `render` is set to `true`.
|
|
1884
|
+
*
|
|
1885
|
+
* @internal
|
|
1886
|
+
*/
|
|
1887
|
+
this.renderSubscribers = new SubscriptionManager();
|
|
1888
|
+
/**
|
|
1889
|
+
* Tracks whether this value can output a velocity. Currently this is only true
|
|
1890
|
+
* if the value is numerical, but we might be able to widen the scope here and support
|
|
1891
|
+
* other value types.
|
|
1892
|
+
*
|
|
1893
|
+
* @internal
|
|
1894
|
+
*/
|
|
1895
|
+
this.canTrackVelocity = false;
|
|
1896
|
+
this.updateAndNotify = (v, render = true) => {
|
|
1897
|
+
this.prev = this.current;
|
|
1898
|
+
this.current = v;
|
|
1899
|
+
// Update timestamp
|
|
1900
|
+
const { delta, timestamp } = frameData;
|
|
1901
|
+
if (this.lastUpdated !== timestamp) {
|
|
1902
|
+
this.timeDelta = delta;
|
|
1903
|
+
this.lastUpdated = timestamp;
|
|
1904
|
+
sync.postRender(this.scheduleVelocityCheck);
|
|
1905
|
+
}
|
|
1906
|
+
// Update update subscribers
|
|
1907
|
+
if (this.prev !== this.current) {
|
|
1908
|
+
this.updateSubscribers.notify(this.current);
|
|
1909
|
+
}
|
|
1910
|
+
// Update velocity subscribers
|
|
1911
|
+
if (this.velocityUpdateSubscribers.getSize()) {
|
|
1912
|
+
this.velocityUpdateSubscribers.notify(this.getVelocity());
|
|
1913
|
+
}
|
|
1914
|
+
// Update render subscribers
|
|
1915
|
+
if (render) {
|
|
1916
|
+
this.renderSubscribers.notify(this.current);
|
|
1917
|
+
}
|
|
1918
|
+
};
|
|
1919
|
+
/**
|
|
1920
|
+
* Schedule a velocity check for the next frame.
|
|
1921
|
+
*
|
|
1922
|
+
* This is an instanced and bound function to prevent generating a new
|
|
1923
|
+
* function once per frame.
|
|
1924
|
+
*
|
|
1925
|
+
* @internal
|
|
1926
|
+
*/
|
|
1927
|
+
this.scheduleVelocityCheck = () => sync.postRender(this.velocityCheck);
|
|
1928
|
+
/**
|
|
1929
|
+
* Updates `prev` with `current` if the value hasn't been updated this frame.
|
|
1930
|
+
* This ensures velocity calculations return `0`.
|
|
1931
|
+
*
|
|
1932
|
+
* This is an instanced and bound function to prevent generating a new
|
|
1933
|
+
* function once per frame.
|
|
1934
|
+
*
|
|
1935
|
+
* @internal
|
|
1936
|
+
*/
|
|
1937
|
+
this.velocityCheck = ({ timestamp }) => {
|
|
1938
|
+
if (timestamp !== this.lastUpdated) {
|
|
1939
|
+
this.prev = this.current;
|
|
1940
|
+
this.velocityUpdateSubscribers.notify(this.getVelocity());
|
|
1941
|
+
}
|
|
1942
|
+
};
|
|
1943
|
+
this.hasAnimated = false;
|
|
1944
|
+
this.prev = this.current = init;
|
|
1945
|
+
this.canTrackVelocity = isFloat(this.current);
|
|
1946
|
+
this.owner = options.owner;
|
|
1751
1947
|
}
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1948
|
+
/**
|
|
1949
|
+
* Adds a function that will be notified when the `MotionValue` is updated.
|
|
1950
|
+
*
|
|
1951
|
+
* It returns a function that, when called, will cancel the subscription.
|
|
1952
|
+
*
|
|
1953
|
+
* When calling `onChange` inside a React component, it should be wrapped with the
|
|
1954
|
+
* `useEffect` hook. As it returns an unsubscribe function, this should be returned
|
|
1955
|
+
* from the `useEffect` function to ensure you don't add duplicate subscribers..
|
|
1956
|
+
*
|
|
1957
|
+
* ```jsx
|
|
1958
|
+
* export const MyComponent = () => {
|
|
1959
|
+
* const x = useMotionValue(0)
|
|
1960
|
+
* const y = useMotionValue(0)
|
|
1961
|
+
* const opacity = useMotionValue(1)
|
|
1962
|
+
*
|
|
1963
|
+
* useEffect(() => {
|
|
1964
|
+
* function updateOpacity() {
|
|
1965
|
+
* const maxXY = Math.max(x.get(), y.get())
|
|
1966
|
+
* const newOpacity = transform(maxXY, [0, 100], [1, 0])
|
|
1967
|
+
* opacity.set(newOpacity)
|
|
1968
|
+
* }
|
|
1969
|
+
*
|
|
1970
|
+
* const unsubscribeX = x.onChange(updateOpacity)
|
|
1971
|
+
* const unsubscribeY = y.onChange(updateOpacity)
|
|
1972
|
+
*
|
|
1973
|
+
* return () => {
|
|
1974
|
+
* unsubscribeX()
|
|
1975
|
+
* unsubscribeY()
|
|
1976
|
+
* }
|
|
1977
|
+
* }, [])
|
|
1978
|
+
*
|
|
1979
|
+
* return <motion.div style={{ x }} />
|
|
1980
|
+
* }
|
|
1981
|
+
* ```
|
|
1982
|
+
*
|
|
1983
|
+
* @privateRemarks
|
|
1984
|
+
*
|
|
1985
|
+
* We could look into a `useOnChange` hook if the above lifecycle management proves confusing.
|
|
1986
|
+
*
|
|
1987
|
+
* ```jsx
|
|
1988
|
+
* useOnChange(x, () => {})
|
|
1989
|
+
* ```
|
|
1990
|
+
*
|
|
1991
|
+
* @param subscriber - A function that receives the latest value.
|
|
1992
|
+
* @returns A function that, when called, will cancel this subscription.
|
|
1993
|
+
*
|
|
1994
|
+
* @public
|
|
1995
|
+
*/
|
|
1996
|
+
onChange(subscription) {
|
|
1997
|
+
return this.updateSubscribers.add(subscription);
|
|
1765
1998
|
}
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
type: "spring",
|
|
1769
|
-
stiffness: bounceStiffness,
|
|
1770
|
-
damping: bounceDamping,
|
|
1771
|
-
restDelta,
|
|
1772
|
-
...options,
|
|
1773
|
-
});
|
|
1999
|
+
clearListeners() {
|
|
2000
|
+
this.updateSubscribers.clear();
|
|
1774
2001
|
}
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
2002
|
+
/**
|
|
2003
|
+
* Adds a function that will be notified when the `MotionValue` requests a render.
|
|
2004
|
+
*
|
|
2005
|
+
* @param subscriber - A function that's provided the latest value.
|
|
2006
|
+
* @returns A function that, when called, will cancel this subscription.
|
|
2007
|
+
*
|
|
2008
|
+
* @internal
|
|
2009
|
+
*/
|
|
2010
|
+
onRenderRequest(subscription) {
|
|
2011
|
+
// Render immediately
|
|
2012
|
+
subscription(this.get());
|
|
2013
|
+
return this.renderSubscribers.add(subscription);
|
|
1778
2014
|
}
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
* animation
|
|
1787
|
-
*/
|
|
1788
|
-
let target = power * velocity + from;
|
|
1789
|
-
if (typeof modifyTarget !== "undefined")
|
|
1790
|
-
target = modifyTarget(target);
|
|
1791
|
-
const boundary = boundaryNearest(target);
|
|
1792
|
-
const heading = boundary === min ? -1 : 1;
|
|
1793
|
-
let prev;
|
|
1794
|
-
let current;
|
|
1795
|
-
const checkBoundary = (v) => {
|
|
1796
|
-
prev = current;
|
|
1797
|
-
current = v;
|
|
1798
|
-
velocity = velocityPerSecond(v - prev, frameData.delta);
|
|
1799
|
-
if ((heading === 1 && v > boundary) ||
|
|
1800
|
-
(heading === -1 && v < boundary)) {
|
|
1801
|
-
startSpring({ from: v, to: boundary, velocity });
|
|
1802
|
-
}
|
|
1803
|
-
};
|
|
1804
|
-
startAnimation({
|
|
1805
|
-
type: "decay",
|
|
1806
|
-
from,
|
|
1807
|
-
velocity,
|
|
1808
|
-
timeConstant,
|
|
1809
|
-
power,
|
|
1810
|
-
restDelta,
|
|
1811
|
-
modifyTarget,
|
|
1812
|
-
onUpdate: isOutOfBounds(target) ? checkBoundary : undefined,
|
|
1813
|
-
});
|
|
2015
|
+
/**
|
|
2016
|
+
* Attaches a passive effect to the `MotionValue`.
|
|
2017
|
+
*
|
|
2018
|
+
* @internal
|
|
2019
|
+
*/
|
|
2020
|
+
attach(passiveEffect) {
|
|
2021
|
+
this.passiveEffect = passiveEffect;
|
|
1814
2022
|
}
|
|
1815
|
-
return {
|
|
1816
|
-
stop: () => currentAnimation === null || currentAnimation === void 0 ? void 0 : currentAnimation.stop(),
|
|
1817
|
-
};
|
|
1818
|
-
}
|
|
1819
|
-
|
|
1820
|
-
/**
|
|
1821
|
-
* Timeout defined in ms
|
|
1822
|
-
*/
|
|
1823
|
-
function delay(callback, timeout) {
|
|
1824
|
-
const start = performance.now();
|
|
1825
|
-
const checkElapsed = ({ timestamp }) => {
|
|
1826
|
-
const elapsed = timestamp - start;
|
|
1827
|
-
if (elapsed >= timeout) {
|
|
1828
|
-
cancelSync.read(checkElapsed);
|
|
1829
|
-
callback(elapsed - timeout);
|
|
1830
|
-
}
|
|
1831
|
-
};
|
|
1832
|
-
sync.read(checkElapsed, true);
|
|
1833
|
-
return () => cancelSync.read(checkElapsed);
|
|
1834
|
-
}
|
|
1835
|
-
|
|
1836
|
-
/**
|
|
1837
|
-
* Decide whether a transition is defined on a given Transition.
|
|
1838
|
-
* This filters out orchestration options and returns true
|
|
1839
|
-
* if any options are left.
|
|
1840
|
-
*/
|
|
1841
|
-
function isTransitionDefined({ when, delay: _delay, delayChildren, staggerChildren, staggerDirection, repeat, repeatType, repeatDelay, from, ...transition }) {
|
|
1842
|
-
return !!Object.keys(transition).length;
|
|
1843
|
-
}
|
|
1844
|
-
/**
|
|
1845
|
-
* Convert Framer Motion's Transition type into Popmotion-compatible options.
|
|
1846
|
-
*/
|
|
1847
|
-
function convertTransitionToAnimationOptions({ ease, times, ...transition }) {
|
|
1848
|
-
const options = { ...transition };
|
|
1849
|
-
if (times)
|
|
1850
|
-
options["offset"] = times;
|
|
1851
2023
|
/**
|
|
1852
|
-
*
|
|
2024
|
+
* Sets the state of the `MotionValue`.
|
|
2025
|
+
*
|
|
2026
|
+
* @remarks
|
|
2027
|
+
*
|
|
2028
|
+
* ```jsx
|
|
2029
|
+
* const x = useMotionValue(0)
|
|
2030
|
+
* x.set(10)
|
|
2031
|
+
* ```
|
|
2032
|
+
*
|
|
2033
|
+
* @param latest - Latest value to set.
|
|
2034
|
+
* @param render - Whether to notify render subscribers. Defaults to `true`
|
|
2035
|
+
*
|
|
2036
|
+
* @public
|
|
1853
2037
|
*/
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
2038
|
+
set(v, render = true) {
|
|
2039
|
+
if (!render || !this.passiveEffect) {
|
|
2040
|
+
this.updateAndNotify(v, render);
|
|
2041
|
+
}
|
|
2042
|
+
else {
|
|
2043
|
+
this.passiveEffect(v, this.updateAndNotify);
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
setWithVelocity(prev, current, delta) {
|
|
2047
|
+
this.set(current);
|
|
2048
|
+
this.prev = prev;
|
|
2049
|
+
this.timeDelta = delta;
|
|
2050
|
+
}
|
|
1858
2051
|
/**
|
|
1859
|
-
*
|
|
2052
|
+
* Returns the latest state of `MotionValue`
|
|
2053
|
+
*
|
|
2054
|
+
* @returns - The latest state of `MotionValue`
|
|
2055
|
+
*
|
|
2056
|
+
* @public
|
|
1860
2057
|
*/
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
? ease.map(easingDefinitionToFunction)
|
|
1864
|
-
: easingDefinitionToFunction(ease);
|
|
2058
|
+
get() {
|
|
2059
|
+
return this.current;
|
|
1865
2060
|
}
|
|
1866
2061
|
/**
|
|
1867
|
-
*
|
|
2062
|
+
* @public
|
|
1868
2063
|
*/
|
|
1869
|
-
|
|
1870
|
-
|
|
2064
|
+
getPrevious() {
|
|
2065
|
+
return this.prev;
|
|
2066
|
+
}
|
|
1871
2067
|
/**
|
|
1872
|
-
*
|
|
1873
|
-
*
|
|
1874
|
-
*
|
|
2068
|
+
* Returns the latest velocity of `MotionValue`
|
|
2069
|
+
*
|
|
2070
|
+
* @returns - The latest velocity of `MotionValue`. Returns `0` if the state is non-numerical.
|
|
2071
|
+
*
|
|
2072
|
+
* @public
|
|
1875
2073
|
*/
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
*/
|
|
1883
|
-
function getDelayFromTransition(transition, key) {
|
|
1884
|
-
const valueTransition = getValueTransition(transition, key) || {};
|
|
1885
|
-
return valueTransition.delay !== undefined
|
|
1886
|
-
? valueTransition.delay
|
|
1887
|
-
: transition.delay !== undefined
|
|
1888
|
-
? transition.delay
|
|
2074
|
+
getVelocity() {
|
|
2075
|
+
// This could be isFloat(this.prev) && isFloat(this.current), but that would be wasteful
|
|
2076
|
+
return this.canTrackVelocity
|
|
2077
|
+
? // These casts could be avoided if parseFloat would be typed better
|
|
2078
|
+
velocityPerSecond(parseFloat(this.current) -
|
|
2079
|
+
parseFloat(this.prev), this.timeDelta)
|
|
1889
2080
|
: 0;
|
|
1890
|
-
}
|
|
1891
|
-
function hydrateKeyframes(options) {
|
|
1892
|
-
if (Array.isArray(options.to) && options.to[0] === null) {
|
|
1893
|
-
options.to = [...options.to];
|
|
1894
|
-
options.to[0] = options.from;
|
|
1895
|
-
}
|
|
1896
|
-
return options;
|
|
1897
|
-
}
|
|
1898
|
-
function getPopmotionAnimationOptions(transition, options, key) {
|
|
1899
|
-
if (Array.isArray(options.to) && transition.duration === undefined) {
|
|
1900
|
-
transition.duration = 0.8;
|
|
1901
2081
|
}
|
|
1902
|
-
hydrateKeyframes(options);
|
|
1903
2082
|
/**
|
|
1904
|
-
*
|
|
2083
|
+
* Registers a new animation to control this `MotionValue`. Only one
|
|
2084
|
+
* animation can drive a `MotionValue` at one time.
|
|
2085
|
+
*
|
|
2086
|
+
* ```jsx
|
|
2087
|
+
* value.start()
|
|
2088
|
+
* ```
|
|
2089
|
+
*
|
|
2090
|
+
* @param animation - A function that starts the provided animation
|
|
2091
|
+
*
|
|
2092
|
+
* @internal
|
|
1905
2093
|
*/
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
2094
|
+
start(animation) {
|
|
2095
|
+
this.stop();
|
|
2096
|
+
return new Promise((resolve) => {
|
|
2097
|
+
this.hasAnimated = true;
|
|
2098
|
+
this.stopAnimation = animation(resolve);
|
|
2099
|
+
}).then(() => this.clearAnimation());
|
|
1911
2100
|
}
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
const valueTransition = getValueTransition(transition, key) || {};
|
|
1922
|
-
const { elapsed = 0 } = transition;
|
|
1923
|
-
valueTransition.elapsed =
|
|
1924
|
-
elapsed - secondsToMilliseconds(transition.delay || 0);
|
|
1925
|
-
let origin = valueTransition.from !== undefined ? valueTransition.from : value.get();
|
|
1926
|
-
const isTargetAnimatable = isAnimatable(key, target);
|
|
1927
|
-
if (origin === "none" && isTargetAnimatable && typeof target === "string") {
|
|
1928
|
-
/**
|
|
1929
|
-
* If we're trying to animate from "none", try and get an animatable version
|
|
1930
|
-
* of the target. This could be improved to work both ways.
|
|
1931
|
-
*/
|
|
1932
|
-
origin = getAnimatableNone(key, target);
|
|
2101
|
+
/**
|
|
2102
|
+
* Stop the currently active animation.
|
|
2103
|
+
*
|
|
2104
|
+
* @public
|
|
2105
|
+
*/
|
|
2106
|
+
stop() {
|
|
2107
|
+
if (this.stopAnimation)
|
|
2108
|
+
this.stopAnimation();
|
|
2109
|
+
this.clearAnimation();
|
|
1933
2110
|
}
|
|
1934
|
-
|
|
1935
|
-
|
|
2111
|
+
/**
|
|
2112
|
+
* Returns `true` if this value is currently animating.
|
|
2113
|
+
*
|
|
2114
|
+
* @public
|
|
2115
|
+
*/
|
|
2116
|
+
isAnimating() {
|
|
2117
|
+
return !!this.stopAnimation;
|
|
1936
2118
|
}
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
typeof origin === "string") {
|
|
1940
|
-
target = getZeroUnit(origin);
|
|
2119
|
+
clearAnimation() {
|
|
2120
|
+
this.stopAnimation = null;
|
|
1941
2121
|
}
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
: animate$1({
|
|
1956
|
-
...getPopmotionAnimationOptions(valueTransition, options, key),
|
|
1957
|
-
onUpdate: (v) => {
|
|
1958
|
-
options.onUpdate(v);
|
|
1959
|
-
valueTransition.onUpdate &&
|
|
1960
|
-
valueTransition.onUpdate(v);
|
|
1961
|
-
},
|
|
1962
|
-
onComplete: () => {
|
|
1963
|
-
options.onComplete();
|
|
1964
|
-
valueTransition.onComplete &&
|
|
1965
|
-
valueTransition.onComplete();
|
|
1966
|
-
},
|
|
1967
|
-
});
|
|
1968
|
-
return () => animation.stop();
|
|
1969
|
-
}
|
|
1970
|
-
function set() {
|
|
1971
|
-
const finalTarget = resolveFinalValueInKeyframes(target);
|
|
1972
|
-
value.set(finalTarget);
|
|
1973
|
-
onComplete();
|
|
1974
|
-
valueTransition.onUpdate && valueTransition.onUpdate(finalTarget);
|
|
1975
|
-
valueTransition.onComplete && valueTransition.onComplete();
|
|
1976
|
-
return () => { };
|
|
2122
|
+
/**
|
|
2123
|
+
* Destroy and clean up subscribers to this `MotionValue`.
|
|
2124
|
+
*
|
|
2125
|
+
* The `MotionValue` hooks like `useMotionValue` and `useTransform` automatically
|
|
2126
|
+
* handle the lifecycle of the returned `MotionValue`, so this method is only necessary if you've manually
|
|
2127
|
+
* created a `MotionValue` via the `motionValue` function.
|
|
2128
|
+
*
|
|
2129
|
+
* @public
|
|
2130
|
+
*/
|
|
2131
|
+
destroy() {
|
|
2132
|
+
this.updateSubscribers.clear();
|
|
2133
|
+
this.renderSubscribers.clear();
|
|
2134
|
+
this.stop();
|
|
1977
2135
|
}
|
|
1978
|
-
const useInstantAnimation = !isOriginAnimatable ||
|
|
1979
|
-
!isTargetAnimatable ||
|
|
1980
|
-
valueTransition.type === false;
|
|
1981
|
-
return useInstantAnimation
|
|
1982
|
-
? valueTransition.elapsed
|
|
1983
|
-
? () => delay(set, -valueTransition.elapsed)
|
|
1984
|
-
: set()
|
|
1985
|
-
: start();
|
|
1986
|
-
}
|
|
1987
|
-
function isZero(value) {
|
|
1988
|
-
return (value === 0 ||
|
|
1989
|
-
(typeof value === "string" &&
|
|
1990
|
-
parseFloat(value) === 0 &&
|
|
1991
|
-
value.indexOf(" ") === -1));
|
|
1992
|
-
}
|
|
1993
|
-
function getZeroUnit(potentialUnitType) {
|
|
1994
|
-
return typeof potentialUnitType === "number"
|
|
1995
|
-
? 0
|
|
1996
|
-
: getAnimatableNone("", potentialUnitType);
|
|
1997
|
-
}
|
|
1998
|
-
function getValueTransition(transition, key) {
|
|
1999
|
-
return transition[key] || transition["default"] || transition;
|
|
2000
2136
|
}
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
* Framer Motion and Popmotion
|
|
2004
|
-
*/
|
|
2005
|
-
function startAnimation(key, value, target, transition = {}) {
|
|
2006
|
-
if (instantAnimationState.current) {
|
|
2007
|
-
transition = { type: false };
|
|
2008
|
-
}
|
|
2009
|
-
return value.start((onComplete) => {
|
|
2010
|
-
return getAnimation(key, value, target, { ...transition, delay: getDelayFromTransition(transition, key) }, onComplete);
|
|
2011
|
-
});
|
|
2137
|
+
function motionValue(init, options) {
|
|
2138
|
+
return new MotionValue(init, options);
|
|
2012
2139
|
}
|
|
2013
2140
|
|
|
2141
|
+
const isMotionValue = (value) => !!(value === null || value === void 0 ? void 0 : value.getVelocity);
|
|
2142
|
+
|
|
2014
2143
|
/**
|
|
2015
2144
|
* Animate a single value or a `MotionValue`.
|
|
2016
2145
|
*
|
|
@@ -2040,7 +2169,7 @@
|
|
|
2040
2169
|
*/
|
|
2041
2170
|
function animate(from, to, transition = {}) {
|
|
2042
2171
|
const value = isMotionValue(from) ? from : motionValue(from);
|
|
2043
|
-
|
|
2172
|
+
value.start(createMotionValueAnimation("", value, to, transition));
|
|
2044
2173
|
return {
|
|
2045
2174
|
stop: () => value.stop(),
|
|
2046
2175
|
isAnimating: () => value.isAnimating(),
|
|
@@ -2584,6 +2713,14 @@
|
|
|
2584
2713
|
}
|
|
2585
2714
|
}
|
|
2586
2715
|
|
|
2716
|
+
const isKeyframesTarget = (v) => {
|
|
2717
|
+
return Array.isArray(v);
|
|
2718
|
+
};
|
|
2719
|
+
|
|
2720
|
+
const isCustomValue = (v) => {
|
|
2721
|
+
return Boolean(v && typeof v === "object" && v.mix && v.toValue);
|
|
2722
|
+
};
|
|
2723
|
+
|
|
2587
2724
|
/**
|
|
2588
2725
|
* If the provided value is a MotionValue, this returns the actual value, otherwise just the value itself
|
|
2589
2726
|
*
|
|
@@ -4453,7 +4590,7 @@
|
|
|
4453
4590
|
else if (!findValueType(value) && complex.test(targetValue)) {
|
|
4454
4591
|
value = getAnimatableNone(key, targetValue);
|
|
4455
4592
|
}
|
|
4456
|
-
visualElement.addValue(key, motionValue(value));
|
|
4593
|
+
visualElement.addValue(key, motionValue(value, { owner: visualElement }));
|
|
4457
4594
|
if (origin[key] === undefined) {
|
|
4458
4595
|
origin[key] = value;
|
|
4459
4596
|
}
|
|
@@ -4866,7 +5003,7 @@
|
|
|
4866
5003
|
* and warn against mismatches.
|
|
4867
5004
|
*/
|
|
4868
5005
|
{
|
|
4869
|
-
warnOnce(nextValue.version === "7.
|
|
5006
|
+
warnOnce(nextValue.version === "7.9.0", `Attempting to mix Framer Motion versions ${nextValue.version} with 7.9.0 may not work as expected.`);
|
|
4870
5007
|
}
|
|
4871
5008
|
}
|
|
4872
5009
|
else if (isMotionValue(prevValue)) {
|
|
@@ -4874,7 +5011,7 @@
|
|
|
4874
5011
|
* If we're swapping from a motion value to a static value,
|
|
4875
5012
|
* create a new motion value from that
|
|
4876
5013
|
*/
|
|
4877
|
-
element.addValue(key, motionValue(nextValue));
|
|
5014
|
+
element.addValue(key, motionValue(nextValue, { owner: element }));
|
|
4878
5015
|
if (isWillChangeMotionValue(willChange)) {
|
|
4879
5016
|
willChange.remove(key);
|
|
4880
5017
|
}
|
|
@@ -5289,7 +5426,7 @@
|
|
|
5289
5426
|
}
|
|
5290
5427
|
let value = this.values.get(key);
|
|
5291
5428
|
if (value === undefined && defaultValue !== undefined) {
|
|
5292
|
-
value = motionValue(defaultValue);
|
|
5429
|
+
value = motionValue(defaultValue, { owner: this });
|
|
5293
5430
|
this.addValue(key, value);
|
|
5294
5431
|
}
|
|
5295
5432
|
return value;
|