motion 10.2.1 → 10.3.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/CHANGELOG.md +10 -0
- package/README.md +1 -0
- package/dist/main.cjs.js +2 -0
- package/dist/main.es.js +1 -0
- package/dist/motion.min.js +1 -1
- package/dist/motion.umd.js +426 -280
- package/dist/size-animate-dom.js +1 -1
- package/dist/size-animate-style.js +1 -1
- package/dist/size-react.js +1 -1
- package/dist/size-spring.js +1 -1
- package/dist/size-timeline-dom.js +1 -1
- package/dist/size-webpack-animate.js +1 -1
- package/dist/targets/dom/animate-style.cjs.js +135 -134
- package/dist/targets/dom/animate-style.es.js +137 -136
- package/dist/targets/dom/animate.cjs.js +15 -4
- package/dist/targets/dom/animate.es.js +16 -5
- package/dist/targets/dom/data.cjs.js +4 -3
- package/dist/targets/dom/data.es.js +4 -3
- package/dist/targets/dom/style.cjs.js +1 -1
- package/dist/targets/dom/style.es.js +2 -2
- package/dist/targets/dom/timeline/index.cjs.js +7 -6
- package/dist/targets/dom/timeline/index.es.js +8 -7
- package/dist/targets/dom/timeline/utils/calc-time.cjs.js +3 -1
- package/dist/targets/dom/timeline/utils/calc-time.es.js +3 -1
- package/dist/targets/dom/utils/apply.cjs.js +4 -8
- package/dist/targets/dom/utils/apply.es.js +3 -7
- package/dist/targets/dom/utils/controls.cjs.js +6 -2
- package/dist/targets/dom/utils/controls.es.js +6 -2
- package/dist/targets/dom/utils/css-var.cjs.js +2 -2
- package/dist/targets/dom/utils/css-var.es.js +3 -3
- package/dist/targets/dom/utils/easing.cjs.js +4 -2
- package/dist/targets/dom/utils/easing.es.js +4 -2
- package/dist/targets/dom/utils/feature-detection.cjs.js +4 -4
- package/dist/targets/dom/utils/feature-detection.es.js +4 -4
- package/dist/targets/dom/utils/get-style-name.cjs.js +13 -0
- package/dist/targets/dom/utils/get-style-name.es.js +9 -0
- package/dist/targets/dom/utils/keyframes.cjs.js +2 -4
- package/dist/targets/dom/utils/keyframes.es.js +2 -4
- package/dist/targets/dom/utils/options.cjs.js +1 -1
- package/dist/targets/dom/utils/options.es.js +1 -1
- package/dist/targets/dom/utils/stop-animation.cjs.js +2 -0
- package/dist/targets/dom/utils/stop-animation.es.js +2 -0
- package/dist/targets/dom/utils/transforms.cjs.js +10 -7
- package/dist/targets/dom/utils/transforms.es.js +10 -7
- package/dist/targets/js/{animate-number.cjs.js → NumberAnimation.cjs.js} +40 -29
- package/dist/targets/js/{animate-number.es.js → NumberAnimation.es.js} +40 -28
- package/dist/targets/js/easing/glide/generator.cjs.js +99 -0
- package/dist/targets/js/easing/glide/generator.es.js +95 -0
- package/dist/targets/js/easing/glide/index.cjs.js +10 -0
- package/dist/targets/js/easing/glide/index.es.js +6 -0
- package/dist/targets/js/easing/spring/generator.cjs.js +9 -4
- package/dist/targets/js/easing/spring/generator.es.js +9 -5
- package/dist/targets/js/easing/spring/index.cjs.js +2 -62
- package/dist/targets/js/easing/spring/index.es.js +2 -62
- package/dist/targets/js/easing/utils/create-generator-easing.cjs.js +71 -0
- package/dist/targets/js/easing/utils/create-generator-easing.es.js +67 -0
- package/dist/targets/js/easing/utils/has-reached-target.cjs.js +10 -0
- package/dist/targets/js/easing/utils/has-reached-target.es.js +6 -0
- package/dist/targets/js/easing/utils/pregenerate-keyframes.cjs.js +13 -10
- package/dist/targets/js/easing/utils/pregenerate-keyframes.es.js +13 -10
- package/dist/targets/react/hooks/use-animation.cjs.js +5 -2
- package/dist/targets/react/hooks/use-animation.es.js +5 -2
- package/dist/targets/react/utils/keyframes.cjs.js +5 -3
- package/dist/targets/react/utils/keyframes.es.js +6 -4
- package/dist/utils/is-number.cjs.js +7 -0
- package/dist/utils/is-number.es.js +3 -0
- package/dist/utils/stagger.cjs.js +2 -1
- package/dist/utils/stagger.es.js +2 -1
- package/package.json +4 -10
- package/types/index.d.ts +1 -0
- package/types/targets/dom/animate-style.d.ts +2 -2
- package/types/targets/dom/style.d.ts +1 -1
- package/types/targets/dom/types.d.ts +8 -4
- package/types/targets/dom/utils/apply.d.ts +3 -2
- package/types/targets/dom/utils/controls.d.ts +3 -3
- package/types/targets/dom/utils/get-style-name.d.ts +1 -0
- package/types/targets/dom/utils/keyframes.d.ts +1 -1
- package/types/targets/dom/utils/stop-animation.d.ts +1 -1
- package/types/targets/dom/utils/transforms.d.ts +2 -2
- package/types/targets/js/{animate-number.d.ts → NumberAnimation.d.ts} +2 -3
- package/types/targets/js/easing/glide/generator.d.ts +5 -0
- package/types/targets/js/easing/glide/index.d.ts +2 -0
- package/types/targets/js/easing/glide/types.d.ts +14 -0
- package/types/targets/js/easing/spring/generator.d.ts +1 -0
- package/types/targets/js/easing/spring/index.d.ts +1 -2
- package/types/targets/js/easing/utils/create-generator-easing.d.ts +3 -0
- package/types/targets/js/easing/utils/has-reached-target.d.ts +1 -0
- package/types/targets/js/easing/utils/pregenerate-keyframes.d.ts +1 -1
- package/types/targets/js/types.d.ts +3 -0
- package/types/utils/is-number.d.ts +1 -0
package/dist/motion.umd.js
CHANGED
|
@@ -8,9 +8,10 @@
|
|
|
8
8
|
function getAnimationData(element) {
|
|
9
9
|
if (!data.has(element)) {
|
|
10
10
|
data.set(element, {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
transforms: [],
|
|
12
|
+
animations: {},
|
|
13
|
+
generators: {},
|
|
14
|
+
prevGeneratorState: {},
|
|
14
15
|
});
|
|
15
16
|
}
|
|
16
17
|
return data.get(element);
|
|
@@ -61,7 +62,7 @@
|
|
|
61
62
|
},
|
|
62
63
|
skew: rotation,
|
|
63
64
|
};
|
|
64
|
-
const
|
|
65
|
+
const transformDefinitions = new Map();
|
|
65
66
|
const asTransformCssVar = (name) => `--motion-${name}`;
|
|
66
67
|
/**
|
|
67
68
|
* Generate a list of every possible transform key
|
|
@@ -70,7 +71,7 @@
|
|
|
70
71
|
order.forEach((name) => {
|
|
71
72
|
axes.forEach((axis) => {
|
|
72
73
|
transforms.push(name + axis);
|
|
73
|
-
|
|
74
|
+
transformDefinitions.set(asTransformCssVar(name + axis), baseTransformProperties[name]);
|
|
74
75
|
});
|
|
75
76
|
});
|
|
76
77
|
/**
|
|
@@ -83,11 +84,14 @@
|
|
|
83
84
|
const transformLookup = new Set(transforms);
|
|
84
85
|
const isTransform = (name) => transformLookup.has(name);
|
|
85
86
|
const addTransformToElement = (element, name) => {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
// Map x to translateX etc
|
|
88
|
+
if (transformAlias[name])
|
|
89
|
+
name = transformAlias[name];
|
|
90
|
+
const { transforms } = getAnimationData(element);
|
|
91
|
+
addUniqueItem(transforms, name);
|
|
92
|
+
element.style.transform = buildTransformTemplate(transforms);
|
|
89
93
|
};
|
|
90
|
-
const buildTransformTemplate = (
|
|
94
|
+
const buildTransformTemplate = (transforms) => transforms
|
|
91
95
|
.sort(compareTransformOrder)
|
|
92
96
|
.reduce(transformListToString, "")
|
|
93
97
|
.trim();
|
|
@@ -100,8 +104,8 @@
|
|
|
100
104
|
return;
|
|
101
105
|
registeredProperties.add(name);
|
|
102
106
|
try {
|
|
103
|
-
const { syntax, initialValue } =
|
|
104
|
-
?
|
|
107
|
+
const { syntax, initialValue } = transformDefinitions.has(name)
|
|
108
|
+
? transformDefinitions.get(name)
|
|
105
109
|
: {};
|
|
106
110
|
CSS.registerProperty({
|
|
107
111
|
name,
|
|
@@ -115,20 +119,10 @@
|
|
|
115
119
|
|
|
116
120
|
const ms = (seconds) => seconds * 1000;
|
|
117
121
|
|
|
118
|
-
|
|
119
|
-
// Suppress error thrown by WAAPI
|
|
120
|
-
try {
|
|
121
|
-
/**
|
|
122
|
-
* commitStyles has overhead so we only want to commit and cancel
|
|
123
|
-
*/
|
|
124
|
-
animation.playState !== "finished" && animation.commitStyles();
|
|
125
|
-
animation.cancel();
|
|
126
|
-
}
|
|
127
|
-
catch (e) { }
|
|
128
|
-
}
|
|
122
|
+
const isNumber = (value) => typeof value === "number";
|
|
129
123
|
|
|
130
|
-
const isCubicBezier = (easing) => Array.isArray(easing) &&
|
|
131
|
-
const isEasingList = (easing) => Array.isArray(easing) &&
|
|
124
|
+
const isCubicBezier = (easing) => Array.isArray(easing) && isNumber(easing[0]);
|
|
125
|
+
const isEasingList = (easing) => Array.isArray(easing) && !isNumber(easing[0]);
|
|
132
126
|
const isCustomEasing = (easing) => typeof easing === "object" &&
|
|
133
127
|
Boolean(easing.createAnimation);
|
|
134
128
|
const convertEasing = (easing) => isCubicBezier(easing) ? cubicBezierAsString(easing) : easing;
|
|
@@ -151,21 +145,17 @@
|
|
|
151
145
|
finished: () => Boolean(testAnimation({ opacity: [0, 1] }).finished),
|
|
152
146
|
};
|
|
153
147
|
const results = {};
|
|
154
|
-
const supports =
|
|
155
|
-
|
|
148
|
+
const supports = {};
|
|
149
|
+
for (const key in featureTests) {
|
|
150
|
+
supports[key] = () => {
|
|
156
151
|
if (results[key] === undefined)
|
|
157
152
|
results[key] = featureTests[key]();
|
|
158
153
|
return results[key];
|
|
159
154
|
};
|
|
160
|
-
|
|
161
|
-
}, {});
|
|
155
|
+
}
|
|
162
156
|
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
};
|
|
166
|
-
const createStyleRenderer = (element, name) => {
|
|
167
|
-
return (latest) => (element.style[name] = latest);
|
|
168
|
-
};
|
|
157
|
+
const cssVariableRenderer = (element, name) => (latest) => element.style.setProperty(name, latest);
|
|
158
|
+
const styleRenderer = (element, name) => (latest) => (element.style[name] = latest);
|
|
169
159
|
|
|
170
160
|
const defaults = {
|
|
171
161
|
duration: 0.3,
|
|
@@ -349,8 +339,8 @@
|
|
|
349
339
|
};
|
|
350
340
|
}
|
|
351
341
|
|
|
352
|
-
class
|
|
353
|
-
constructor(output, keyframes, { easing = defaults.easing, duration = defaults.duration, delay = defaults.delay, endDelay = defaults.endDelay, repeat = defaults.repeat, offset, direction = "normal", }) {
|
|
342
|
+
class NumberAnimation {
|
|
343
|
+
constructor(output, keyframes = [0, 1], { easing = defaults.easing, duration = defaults.duration, delay = defaults.delay, endDelay = defaults.endDelay, repeat = defaults.repeat, offset, direction = "normal", } = {}) {
|
|
354
344
|
this.startTime = 0;
|
|
355
345
|
this.rate = 1;
|
|
356
346
|
this.t = 0;
|
|
@@ -361,54 +351,69 @@
|
|
|
361
351
|
this.reject = reject;
|
|
362
352
|
});
|
|
363
353
|
const totalDuration = duration * (repeat + 1);
|
|
364
|
-
|
|
365
|
-
|
|
354
|
+
/**
|
|
355
|
+
* We don't currently support custom easing (spring, glide etc) in NumberAnimation
|
|
356
|
+
* (although this is completely possible), so this will have been hydrated by
|
|
357
|
+
* animateStyle.
|
|
358
|
+
*/
|
|
359
|
+
if (isCustomEasing(easing))
|
|
366
360
|
easing = "ease";
|
|
367
|
-
}
|
|
368
361
|
const interpolate = slowInterpolateNumbers(keyframes, offset, isEasingList(easing)
|
|
369
362
|
? easing.map(getEasingFunction)
|
|
370
363
|
: getEasingFunction(easing));
|
|
371
364
|
this.tick = (timestamp) => {
|
|
372
|
-
if (this.
|
|
373
|
-
const latest = interpolate(1);
|
|
374
|
-
output(latest);
|
|
375
|
-
this.resolve(latest);
|
|
376
|
-
return;
|
|
377
|
-
}
|
|
378
|
-
if (this.pauseTime) {
|
|
365
|
+
if (this.pauseTime)
|
|
379
366
|
timestamp = this.pauseTime;
|
|
380
|
-
}
|
|
381
367
|
let t = (timestamp - this.startTime) * this.rate;
|
|
382
368
|
this.t = t;
|
|
383
369
|
// Convert to seconds
|
|
384
370
|
t /= 1000;
|
|
385
371
|
// Rebase on delay
|
|
386
372
|
t = Math.max(t - delay, 0);
|
|
373
|
+
/**
|
|
374
|
+
* If this animation has finished, set the current time
|
|
375
|
+
* to the total duration.
|
|
376
|
+
*/
|
|
377
|
+
if (this.playState === "finished")
|
|
378
|
+
t = totalDuration;
|
|
379
|
+
/**
|
|
380
|
+
* Get the current progress (0-1) of the animation. If t is >
|
|
381
|
+
* than duration we'll get values like 2.5 (midway through the
|
|
382
|
+
* third iteration)
|
|
383
|
+
*/
|
|
387
384
|
const progress = t / duration;
|
|
388
385
|
// TODO progress += iterationStart
|
|
386
|
+
/**
|
|
387
|
+
* Get the current iteration (0 indexed). For instance the floor of
|
|
388
|
+
* 2.5 is 2.
|
|
389
|
+
*/
|
|
389
390
|
let currentIteration = Math.floor(progress);
|
|
391
|
+
/**
|
|
392
|
+
* Get the current progress of the iteration by taking the remainder
|
|
393
|
+
* so 2.5 is 0.5 through iteration 2
|
|
394
|
+
*/
|
|
390
395
|
let iterationProgress = progress % 1.0;
|
|
391
396
|
if (!iterationProgress && progress >= 1) {
|
|
392
397
|
iterationProgress = 1;
|
|
393
398
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
399
|
+
/**
|
|
400
|
+
* If iteration progress is 1 we count that as the end
|
|
401
|
+
* of the previous iteration.
|
|
402
|
+
*/
|
|
403
|
+
iterationProgress === 1 && currentIteration--;
|
|
404
|
+
/**
|
|
405
|
+
* Reverse progress if we're not running in "normal" direction
|
|
406
|
+
*/
|
|
398
407
|
const iterationIsOdd = currentIteration % 2;
|
|
399
408
|
if (direction === "reverse" ||
|
|
400
409
|
(direction === "alternate" && iterationIsOdd) ||
|
|
401
410
|
(direction === "alternate-reverse" && !iterationIsOdd)) {
|
|
402
411
|
iterationProgress = 1 - iterationProgress;
|
|
403
412
|
}
|
|
404
|
-
const
|
|
405
|
-
const interpolationProgress = interpolationIsFinished
|
|
406
|
-
? 1
|
|
407
|
-
: Math.min(iterationProgress, 1);
|
|
408
|
-
const latest = interpolate(interpolationProgress);
|
|
413
|
+
const latest = interpolate(t >= totalDuration ? 1 : Math.min(iterationProgress, 1));
|
|
409
414
|
output(latest);
|
|
410
|
-
const
|
|
411
|
-
if (
|
|
415
|
+
const isAnimationFinished = this.playState === "finished" || t >= totalDuration + endDelay;
|
|
416
|
+
if (isAnimationFinished) {
|
|
412
417
|
this.playState = "finished";
|
|
413
418
|
this.resolve(latest);
|
|
414
419
|
}
|
|
@@ -467,9 +472,16 @@
|
|
|
467
472
|
this.rate = rate;
|
|
468
473
|
}
|
|
469
474
|
}
|
|
470
|
-
|
|
471
|
-
|
|
475
|
+
|
|
476
|
+
function hydrateKeyframes(keyframes, readInitialValue) {
|
|
477
|
+
for (let i = 0; i < keyframes.length; i++) {
|
|
478
|
+
if (keyframes[i] === null) {
|
|
479
|
+
keyframes[i] = i ? keyframes[i - 1] : readInitialValue();
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return keyframes;
|
|
472
483
|
}
|
|
484
|
+
const keyframesList = (keyframes) => Array.isArray(keyframes) ? keyframes : [keyframes];
|
|
473
485
|
|
|
474
486
|
const style = {
|
|
475
487
|
get: (element, name) => {
|
|
@@ -477,7 +489,7 @@
|
|
|
477
489
|
? element.style.getPropertyValue(name)
|
|
478
490
|
: getComputedStyle(element)[name];
|
|
479
491
|
if (!value && value !== 0) {
|
|
480
|
-
const definition =
|
|
492
|
+
const definition = transformDefinitions.get(name);
|
|
481
493
|
if (definition)
|
|
482
494
|
value = definition.initialValue;
|
|
483
495
|
}
|
|
@@ -485,179 +497,188 @@
|
|
|
485
497
|
},
|
|
486
498
|
};
|
|
487
499
|
|
|
488
|
-
function
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
500
|
+
function getStyleName(key) {
|
|
501
|
+
if (transformAlias[key])
|
|
502
|
+
key = transformAlias[key];
|
|
503
|
+
return isTransform(key) ? asTransformCssVar(key) : key;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function stopAnimation(animation) {
|
|
507
|
+
if (!animation)
|
|
508
|
+
return;
|
|
509
|
+
// Suppress error thrown by WAAPI
|
|
510
|
+
try {
|
|
511
|
+
/**
|
|
512
|
+
* commitStyles has overhead so we only want to commit and cancel
|
|
513
|
+
*/
|
|
514
|
+
animation.playState !== "finished" && animation.commitStyles();
|
|
515
|
+
animation.cancel();
|
|
493
516
|
}
|
|
494
|
-
|
|
517
|
+
catch (e) { }
|
|
495
518
|
}
|
|
496
|
-
const keyframesList = (keyframes) => Array.isArray(keyframes) ? keyframes : [keyframes];
|
|
497
519
|
|
|
498
|
-
function animateStyle(element,
|
|
520
|
+
function animateStyle(element, key, keyframesDefinition, options = {}) {
|
|
499
521
|
let animation;
|
|
500
522
|
let { duration = defaults.duration, delay = defaults.delay, endDelay = defaults.endDelay, repeat = defaults.repeat, easing = defaults.easing, direction, offset, allowWebkitAcceleration = false, } = options;
|
|
501
523
|
const data = getAnimationData(element);
|
|
502
524
|
let canAnimateNatively = supports.waapi();
|
|
503
525
|
let render = noop;
|
|
504
|
-
const valueIsTransform = isTransform(
|
|
526
|
+
const valueIsTransform = isTransform(key);
|
|
505
527
|
/**
|
|
506
528
|
* If this is an individual transform, we need to map its
|
|
507
529
|
* key to a CSS variable and update the element's transform style
|
|
508
530
|
*/
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
name = transformAlias[name];
|
|
512
|
-
addTransformToElement(element, name);
|
|
513
|
-
name = asTransformCssVar(name);
|
|
514
|
-
}
|
|
531
|
+
valueIsTransform && addTransformToElement(element, key);
|
|
532
|
+
const name = getStyleName(key);
|
|
515
533
|
/**
|
|
516
534
|
* Get definition of value, this will be used to convert numerical
|
|
517
535
|
* keyframes into the default value type.
|
|
518
536
|
*/
|
|
519
|
-
const definition =
|
|
520
|
-
/**
|
|
521
|
-
* Replace null values with the previous keyframe value, or read
|
|
522
|
-
* it from the DOM if it's the first keyframe.
|
|
523
|
-
*
|
|
524
|
-
* TODO: This needs to come after the valueIsTransform
|
|
525
|
-
* check so it can correctly read the underlying value.
|
|
526
|
-
* Should make a test for this.
|
|
527
|
-
*/
|
|
528
|
-
let keyframes = hydrateKeyframes(keyframesList(keyframesDefinition), element, name);
|
|
529
|
-
if (isCustomEasing(easing)) {
|
|
530
|
-
const custom = easing.createAnimation(keyframes, () => style.get(element, name), valueIsTransform, name, data);
|
|
531
|
-
easing = custom.easing;
|
|
532
|
-
if (custom.keyframes)
|
|
533
|
-
keyframes = custom.keyframes;
|
|
534
|
-
if (custom.duration)
|
|
535
|
-
duration = custom.duration;
|
|
536
|
-
}
|
|
537
|
-
/**
|
|
538
|
-
* If this is a CSS variable we need to register it with the browser
|
|
539
|
-
* before it can be animated natively. We also set it with setProperty
|
|
540
|
-
* rather than directly onto the element.style object.
|
|
541
|
-
*/
|
|
542
|
-
if (isCssVar(name)) {
|
|
543
|
-
render = createCssVariableRenderer(element, name);
|
|
544
|
-
if (supports.cssRegisterProperty()) {
|
|
545
|
-
registerCssVariable(name);
|
|
546
|
-
}
|
|
547
|
-
else {
|
|
548
|
-
canAnimateNatively = false;
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
else {
|
|
552
|
-
render = createStyleRenderer(element, name);
|
|
553
|
-
}
|
|
537
|
+
const definition = transformDefinitions.get(name);
|
|
554
538
|
/**
|
|
555
|
-
* Stop the current animation, if any
|
|
556
|
-
*
|
|
557
|
-
*
|
|
539
|
+
* Stop the current animation, if any. Because this will trigger
|
|
540
|
+
* commitStyles (DOM writes) and we might later trigger DOM reads,
|
|
541
|
+
* this is fired now and we return a factory function to create
|
|
542
|
+
* the actual animation that can get called in batch,
|
|
558
543
|
*/
|
|
559
|
-
|
|
544
|
+
stopAnimation(data.animations[name]);
|
|
560
545
|
/**
|
|
561
|
-
*
|
|
562
|
-
* feature detects CSS.registerProperty but could check WAAPI too.
|
|
546
|
+
* Batchable factory function containing all DOM reads.
|
|
563
547
|
*/
|
|
564
|
-
|
|
548
|
+
return () => {
|
|
549
|
+
const readInitialValue = () => { var _a, _b; return (_b = (_a = style.get(element, name)) !== null && _a !== void 0 ? _a : definition === null || definition === void 0 ? void 0 : definition.initialValue) !== null && _b !== void 0 ? _b : 0; };
|
|
565
550
|
/**
|
|
566
|
-
*
|
|
567
|
-
*
|
|
551
|
+
* Replace null values with the previous keyframe value, or read
|
|
552
|
+
* it from the DOM if it's the first keyframe.
|
|
568
553
|
*/
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
554
|
+
let keyframes = hydrateKeyframes(keyframesList(keyframesDefinition), readInitialValue);
|
|
555
|
+
if (isCustomEasing(easing)) {
|
|
556
|
+
const custom = easing.createAnimation(keyframes, readInitialValue, valueIsTransform, name, data);
|
|
557
|
+
easing = custom.easing;
|
|
558
|
+
if (custom.keyframes !== undefined)
|
|
559
|
+
keyframes = custom.keyframes;
|
|
560
|
+
if (custom.duration !== undefined)
|
|
561
|
+
duration = custom.duration;
|
|
574
562
|
}
|
|
575
|
-
const animationOptions = {
|
|
576
|
-
delay: ms(delay),
|
|
577
|
-
duration: ms(duration),
|
|
578
|
-
endDelay: ms(endDelay),
|
|
579
|
-
easing: !isEasingList(easing) ? convertEasing(easing) : undefined,
|
|
580
|
-
direction,
|
|
581
|
-
iterations: repeat + 1,
|
|
582
|
-
fill: "both",
|
|
583
|
-
};
|
|
584
|
-
animation = element.animate({
|
|
585
|
-
[name]: keyframes,
|
|
586
|
-
offset,
|
|
587
|
-
easing: isEasingList(easing) ? easing.map(convertEasing) : undefined,
|
|
588
|
-
}, animationOptions);
|
|
589
563
|
/**
|
|
590
|
-
*
|
|
564
|
+
* If this is a CSS variable we need to register it with the browser
|
|
565
|
+
* before it can be animated natively. We also set it with setProperty
|
|
566
|
+
* rather than directly onto the element.style object.
|
|
591
567
|
*/
|
|
592
|
-
if (
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
}
|
|
568
|
+
if (isCssVar(name)) {
|
|
569
|
+
render = cssVariableRenderer(element, name);
|
|
570
|
+
if (supports.cssRegisterProperty()) {
|
|
571
|
+
registerCssVariable(name);
|
|
572
|
+
}
|
|
573
|
+
else {
|
|
574
|
+
canAnimateNatively = false;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
else {
|
|
578
|
+
render = styleRenderer(element, name);
|
|
597
579
|
}
|
|
598
|
-
const target = keyframes[keyframes.length - 1];
|
|
599
|
-
animation.finished
|
|
600
|
-
.then(() => {
|
|
601
|
-
// Apply styles to target
|
|
602
|
-
render(target);
|
|
603
|
-
// Ensure fill modes don't persist
|
|
604
|
-
animation.cancel();
|
|
605
|
-
})
|
|
606
|
-
.catch(noop);
|
|
607
580
|
/**
|
|
608
|
-
*
|
|
609
|
-
*
|
|
610
|
-
* https://trac.webkit.org/browser/webkit/trunk/Source/WebCore/platform/graphics/ca/GraphicsLayerCA.cpp?rev=281238#L1099
|
|
611
|
-
*
|
|
612
|
-
* This fixes Webkit's timing bugs, like accelerated animations falling
|
|
613
|
-
* out of sync with main thread animations and massive delays in starting
|
|
614
|
-
* accelerated animations in WKWebView.
|
|
581
|
+
* If we can animate this value with WAAPI, do so. Currently this only
|
|
582
|
+
* feature detects CSS.registerProperty but could check WAAPI too.
|
|
615
583
|
*/
|
|
616
|
-
if (
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
584
|
+
if (canAnimateNatively) {
|
|
585
|
+
/**
|
|
586
|
+
* Convert numbers to default value types. Currently this only supports
|
|
587
|
+
* transforms but it could also support other value types.
|
|
588
|
+
*/
|
|
589
|
+
if (definition) {
|
|
590
|
+
keyframes = keyframes.map((value) => isNumber(value) ? definition.toDefaultUnit(value) : value);
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* If this browser doesn't support partial/implicit keyframes we need to
|
|
594
|
+
* explicitly provide one.
|
|
595
|
+
*/
|
|
596
|
+
if (!supports.partialKeyframes() && keyframes.length === 1) {
|
|
597
|
+
keyframes.unshift(readInitialValue());
|
|
598
|
+
}
|
|
599
|
+
const animationOptions = {
|
|
600
|
+
delay: ms(delay),
|
|
601
|
+
duration: ms(duration),
|
|
602
|
+
endDelay: ms(endDelay),
|
|
603
|
+
easing: !isEasingList(easing) ? convertEasing(easing) : undefined,
|
|
604
|
+
direction,
|
|
605
|
+
iterations: repeat + 1,
|
|
606
|
+
fill: "both",
|
|
607
|
+
};
|
|
608
|
+
animation = element.animate({
|
|
609
|
+
[name]: keyframes,
|
|
610
|
+
offset,
|
|
611
|
+
easing: isEasingList(easing) ? easing.map(convertEasing) : undefined,
|
|
612
|
+
}, animationOptions);
|
|
613
|
+
/**
|
|
614
|
+
* Polyfill finished Promise in browsers that don't support it
|
|
615
|
+
*/
|
|
616
|
+
if (!animation.finished) {
|
|
617
|
+
animation.finished = new Promise((resolve, reject) => {
|
|
618
|
+
animation.onfinish = resolve;
|
|
619
|
+
animation.oncancel = reject;
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
const target = keyframes[keyframes.length - 1];
|
|
623
|
+
animation.finished
|
|
624
|
+
.then(() => {
|
|
625
|
+
// Apply styles to target
|
|
626
|
+
render(target);
|
|
627
|
+
// Ensure fill modes don't persist
|
|
628
|
+
animation.cancel();
|
|
629
|
+
})
|
|
630
|
+
.catch(noop);
|
|
631
|
+
/**
|
|
632
|
+
* This forces Webkit to run animations on the main thread by exploiting
|
|
633
|
+
* this condition:
|
|
634
|
+
* https://trac.webkit.org/browser/webkit/trunk/Source/WebCore/platform/graphics/ca/GraphicsLayerCA.cpp?rev=281238#L1099
|
|
635
|
+
*
|
|
636
|
+
* This fixes Webkit's timing bugs, like accelerated animations falling
|
|
637
|
+
* out of sync with main thread animations and massive delays in starting
|
|
638
|
+
* accelerated animations in WKWebView.
|
|
639
|
+
*/
|
|
640
|
+
if (!allowWebkitAcceleration)
|
|
641
|
+
animation.playbackRate = 1.000001;
|
|
642
|
+
/**
|
|
643
|
+
* If we can't animate the value natively then we can fallback to the numbers-only
|
|
644
|
+
* polyfill for transforms. All keyframes must be numerical.
|
|
645
|
+
*/
|
|
622
646
|
}
|
|
647
|
+
else if (valueIsTransform && keyframes.every(isNumber)) {
|
|
648
|
+
/**
|
|
649
|
+
* If we only have a single keyframe, we need to create an initial keyframe by reading
|
|
650
|
+
* the current value from the DOM.
|
|
651
|
+
*/
|
|
652
|
+
if (keyframes.length === 1) {
|
|
653
|
+
keyframes.unshift(parseFloat(readInitialValue()));
|
|
654
|
+
}
|
|
655
|
+
if (definition) {
|
|
656
|
+
const applyStyle = render;
|
|
657
|
+
render = (v) => applyStyle(definition.toDefaultUnit(v));
|
|
658
|
+
}
|
|
659
|
+
animation = new NumberAnimation(render, keyframes, Object.assign(Object.assign({}, options), { duration,
|
|
660
|
+
easing }));
|
|
661
|
+
}
|
|
662
|
+
else {
|
|
663
|
+
const target = keyframes[keyframes.length - 1];
|
|
664
|
+
render(definition && isNumber(target)
|
|
665
|
+
? definition.toDefaultUnit(target)
|
|
666
|
+
: target);
|
|
667
|
+
}
|
|
668
|
+
data.animations[name] = animation;
|
|
623
669
|
/**
|
|
624
|
-
*
|
|
625
|
-
* their default value type, so here we loop through and map
|
|
626
|
-
* them to numbers.
|
|
670
|
+
* When an animation finishes, delete the reference to the previous animation.
|
|
627
671
|
*/
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
}
|
|
633
|
-
animation
|
|
634
|
-
|
|
635
|
-
}
|
|
636
|
-
else {
|
|
637
|
-
const target = keyframes[keyframes.length - 1];
|
|
638
|
-
render(definition && typeof target === "number"
|
|
639
|
-
? definition.toDefaultUnit(target)
|
|
640
|
-
: target);
|
|
641
|
-
}
|
|
642
|
-
data.activeAnimations[name] = animation;
|
|
643
|
-
/**
|
|
644
|
-
* When an animation finishes, delete the reference to the previous animation.
|
|
645
|
-
*/
|
|
646
|
-
animation === null || animation === void 0 ? void 0 : animation.finished.then(() => clearData(data, name)).catch(noop);
|
|
647
|
-
return animation;
|
|
648
|
-
}
|
|
649
|
-
function stopCurrentAnimation(data, name) {
|
|
650
|
-
if (data.activeAnimations[name]) {
|
|
651
|
-
stopAnimation(data.activeAnimations[name]);
|
|
652
|
-
clearData(data, name);
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
function clearData(data, name) {
|
|
656
|
-
data.activeGenerators[name] = data.activeAnimations[name] = undefined;
|
|
672
|
+
animation === null || animation === void 0 ? void 0 : animation.finished.then(() => {
|
|
673
|
+
data.animations[name] = undefined;
|
|
674
|
+
data.generators[name] = undefined;
|
|
675
|
+
data.prevGeneratorState[name] = undefined;
|
|
676
|
+
}).catch(noop);
|
|
677
|
+
return animation;
|
|
678
|
+
};
|
|
657
679
|
}
|
|
658
|
-
const isNumber = (value) => typeof value === "number";
|
|
659
680
|
|
|
660
|
-
const getOptions = (options, key) => options[key] ? Object.assign(Object.assign({}, options), options[key]) :
|
|
681
|
+
const getOptions = (options, key) => options[key] ? Object.assign(Object.assign({}, options), options[key]) : options;
|
|
661
682
|
|
|
662
683
|
function resolveElements(elements, selectorCache) {
|
|
663
684
|
var _a;
|
|
@@ -676,7 +697,11 @@
|
|
|
676
697
|
return Array.from(elements);
|
|
677
698
|
}
|
|
678
699
|
|
|
679
|
-
const
|
|
700
|
+
const createAnimation = (factory) => factory();
|
|
701
|
+
const createAnimations = (animationFactory, duration) => new Proxy({
|
|
702
|
+
animations: animationFactory.map(createAnimation).filter(Boolean),
|
|
703
|
+
duration,
|
|
704
|
+
}, controls);
|
|
680
705
|
/**
|
|
681
706
|
* TODO:
|
|
682
707
|
* Currently this returns the first animation, ideally it would return
|
|
@@ -723,7 +748,7 @@
|
|
|
723
748
|
|
|
724
749
|
function stagger(duration = 0.1, { start = 0, from = 0, easing } = {}) {
|
|
725
750
|
return (i, total) => {
|
|
726
|
-
const fromIndex =
|
|
751
|
+
const fromIndex = isNumber(from) ? from : getFromIndex(from, total);
|
|
727
752
|
const distance = Math.abs(fromIndex - i);
|
|
728
753
|
let delay = duration * distance;
|
|
729
754
|
if (easing) {
|
|
@@ -752,19 +777,30 @@
|
|
|
752
777
|
function animate(elements, keyframes, options = {}) {
|
|
753
778
|
var _a;
|
|
754
779
|
elements = resolveElements(elements);
|
|
755
|
-
const animations = [];
|
|
756
780
|
const numElements = elements.length;
|
|
781
|
+
/**
|
|
782
|
+
* Create and start new animations
|
|
783
|
+
*/
|
|
784
|
+
const animationFactories = [];
|
|
757
785
|
for (let i = 0; i < numElements; i++) {
|
|
758
786
|
const element = elements[i];
|
|
759
787
|
for (const key in keyframes) {
|
|
760
788
|
const valueOptions = getOptions(options, key);
|
|
761
789
|
valueOptions.delay = resolveOption(valueOptions.delay, i, numElements);
|
|
762
790
|
const animation = animateStyle(element, key, keyframes[key], valueOptions);
|
|
763
|
-
|
|
791
|
+
animationFactories.push(animation);
|
|
764
792
|
}
|
|
765
793
|
}
|
|
766
|
-
return
|
|
767
|
-
|
|
794
|
+
return createAnimations(animationFactories,
|
|
795
|
+
/**
|
|
796
|
+
* TODO:
|
|
797
|
+
* If easing is set to spring or glide, duration will be dynamically
|
|
798
|
+
* generated. Ideally we would dynamically generate this from
|
|
799
|
+
* animation.effect.getComputedTiming().duration but this isn't
|
|
800
|
+
* supported in iOS13 or our number polyfill. Perhaps it's possible
|
|
801
|
+
* to Proxy animations returned from animateStyle that has duration
|
|
802
|
+
* as a getter.
|
|
803
|
+
*/
|
|
768
804
|
(_a = options.duration) !== null && _a !== void 0 ? _a : defaults.duration);
|
|
769
805
|
}
|
|
770
806
|
|
|
@@ -797,7 +833,7 @@
|
|
|
797
833
|
|
|
798
834
|
function calcNextTime(current, next, prev, labels) {
|
|
799
835
|
var _a;
|
|
800
|
-
if (
|
|
836
|
+
if (isNumber(next)) {
|
|
801
837
|
return next;
|
|
802
838
|
}
|
|
803
839
|
else if (next.startsWith("-") || next.startsWith("+")) {
|
|
@@ -848,13 +884,14 @@
|
|
|
848
884
|
|
|
849
885
|
function timeline(definition, options = {}) {
|
|
850
886
|
var _a, _b;
|
|
851
|
-
const animations = [];
|
|
852
887
|
const animationDefinitions = createAnimationsFromTimeline(definition, options);
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
888
|
+
/**
|
|
889
|
+
* Create and start animations
|
|
890
|
+
*/
|
|
891
|
+
const animationFactories = animationDefinitions
|
|
892
|
+
.map((definition) => animateStyle(...definition))
|
|
893
|
+
.filter(Boolean);
|
|
894
|
+
return createAnimations(animationFactories,
|
|
858
895
|
// Get the duration from the first animation definition
|
|
859
896
|
(_b = (_a = animationDefinitions[0]) === null || _a === void 0 ? void 0 : _a[3].duration) !== null && _b !== void 0 ? _b : defaults.duration);
|
|
860
897
|
}
|
|
@@ -1009,6 +1046,11 @@
|
|
|
1009
1046
|
return frameDuration ? velocity * (1000 / frameDuration) : 0;
|
|
1010
1047
|
}
|
|
1011
1048
|
|
|
1049
|
+
function hasReachedTarget(origin, target, current) {
|
|
1050
|
+
return ((origin < target && current >= target) ||
|
|
1051
|
+
(origin > target && current <= target));
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1012
1054
|
const defaultStiffness = 100.0;
|
|
1013
1055
|
const defaultDamping = 10.0;
|
|
1014
1056
|
const defaultMass = 1.0;
|
|
@@ -1016,15 +1058,17 @@
|
|
|
1016
1058
|
const calcAngularFreq = (undampedFreq, dampingRatio) => undampedFreq * Math.sqrt(1 - dampingRatio * dampingRatio);
|
|
1017
1059
|
const createSpringGenerator = ({ stiffness = defaultStiffness, damping = defaultDamping, mass = defaultMass, from = 0, to = 1, velocity = 0.0, restSpeed = 2, restDistance = 0.5, } = {}) => {
|
|
1018
1060
|
velocity = velocity ? velocity / 1000 : 0.0;
|
|
1019
|
-
const dampingRatio = calcDampingRatio(stiffness, damping, mass);
|
|
1020
|
-
const initialDelta = to - from;
|
|
1021
|
-
const undampedAngularFreq = Math.sqrt(stiffness / mass) / 1000;
|
|
1022
|
-
const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
|
|
1023
1061
|
const state = {
|
|
1024
1062
|
done: false,
|
|
1025
1063
|
value: from,
|
|
1064
|
+
target: to,
|
|
1026
1065
|
velocity,
|
|
1066
|
+
hasReachedTarget: false,
|
|
1027
1067
|
};
|
|
1068
|
+
const dampingRatio = calcDampingRatio(stiffness, damping, mass);
|
|
1069
|
+
const initialDelta = to - from;
|
|
1070
|
+
const undampedAngularFreq = Math.sqrt(stiffness / mass) / 1000;
|
|
1071
|
+
const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
|
|
1028
1072
|
let resolveSpring;
|
|
1029
1073
|
if (dampingRatio < 1) {
|
|
1030
1074
|
// Underdamped spring (bouncy)
|
|
@@ -1049,6 +1093,7 @@
|
|
|
1049
1093
|
const isBelowVelocityThreshold = Math.abs(state.velocity) <= restSpeed;
|
|
1050
1094
|
const isBelowDisplacementThreshold = Math.abs(to - state.value) <= restDistance;
|
|
1051
1095
|
state.done = isBelowVelocityThreshold && isBelowDisplacementThreshold;
|
|
1096
|
+
state.hasReachedTarget = hasReachedTarget(from, to, state.value);
|
|
1052
1097
|
return state;
|
|
1053
1098
|
},
|
|
1054
1099
|
};
|
|
@@ -1060,24 +1105,27 @@
|
|
|
1060
1105
|
}
|
|
1061
1106
|
|
|
1062
1107
|
const timeStep = 10;
|
|
1063
|
-
const maxDuration =
|
|
1064
|
-
function pregenerateKeyframes(generator
|
|
1065
|
-
const keyframes = [];
|
|
1108
|
+
const maxDuration = 10000;
|
|
1109
|
+
function pregenerateKeyframes(generator) {
|
|
1066
1110
|
let overshootDuration = undefined;
|
|
1067
|
-
let timestamp =
|
|
1111
|
+
let timestamp = timeStep;
|
|
1068
1112
|
let state = generator.next(0);
|
|
1113
|
+
const keyframes = [state.value];
|
|
1069
1114
|
while (!state.done && timestamp < maxDuration) {
|
|
1070
1115
|
state = generator.next(timestamp);
|
|
1071
|
-
keyframes.push(state.done ? target : state.value);
|
|
1072
|
-
if (overshootDuration === undefined) {
|
|
1073
|
-
|
|
1074
|
-
(origin > target && state.value <= target)) {
|
|
1075
|
-
overshootDuration = timestamp;
|
|
1076
|
-
}
|
|
1116
|
+
keyframes.push(state.done ? state.target : state.value);
|
|
1117
|
+
if (overshootDuration === undefined && state.hasReachedTarget) {
|
|
1118
|
+
overshootDuration = timestamp;
|
|
1077
1119
|
}
|
|
1078
1120
|
timestamp += timeStep;
|
|
1079
1121
|
}
|
|
1080
1122
|
const duration = timestamp - timeStep;
|
|
1123
|
+
/**
|
|
1124
|
+
* If generating an animation that didn't actually move,
|
|
1125
|
+
* generate a second keyframe so we have an origin and target.
|
|
1126
|
+
*/
|
|
1127
|
+
if (keyframes.length === 1)
|
|
1128
|
+
keyframes.push(state.value);
|
|
1081
1129
|
return {
|
|
1082
1130
|
keyframes,
|
|
1083
1131
|
duration: duration / 1000,
|
|
@@ -1085,70 +1133,168 @@
|
|
|
1085
1133
|
};
|
|
1086
1134
|
}
|
|
1087
1135
|
|
|
1088
|
-
function
|
|
1089
|
-
const springCache = new Map();
|
|
1136
|
+
function createGeneratorEasing(createGenerator) {
|
|
1090
1137
|
const keyframesCache = new WeakMap();
|
|
1091
|
-
|
|
1092
|
-
const
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1138
|
+
return (options = {}) => {
|
|
1139
|
+
const generatorCache = new Map();
|
|
1140
|
+
const getGenerator = (from = 0, to = 100, velocity = 0, isScale = false) => {
|
|
1141
|
+
const key = `${from}-${to}-${velocity}-${isScale}`;
|
|
1142
|
+
if (!generatorCache.has(key)) {
|
|
1143
|
+
generatorCache.set(key, createGenerator(Object.assign({ from,
|
|
1144
|
+
to,
|
|
1145
|
+
velocity, restSpeed: isScale ? 0.05 : 2, restDistance: isScale ? 0.01 : 0.5 }, options)));
|
|
1146
|
+
}
|
|
1147
|
+
return generatorCache.get(key);
|
|
1148
|
+
};
|
|
1149
|
+
const getKeyframes = (generator) => {
|
|
1150
|
+
if (!keyframesCache.has(generator)) {
|
|
1151
|
+
keyframesCache.set(generator, pregenerateKeyframes(generator));
|
|
1152
|
+
}
|
|
1153
|
+
return keyframesCache.get(generator);
|
|
1154
|
+
};
|
|
1155
|
+
return {
|
|
1156
|
+
createAnimation: (keyframes, getOrigin, canUseGenerator, name, data) => {
|
|
1157
|
+
let settings;
|
|
1158
|
+
let generator;
|
|
1159
|
+
const numKeyframes = keyframes.length;
|
|
1160
|
+
let shouldUseGenerator = canUseGenerator &&
|
|
1161
|
+
numKeyframes <= 2 &&
|
|
1162
|
+
keyframes.every(isNumberOrNull);
|
|
1163
|
+
if (shouldUseGenerator) {
|
|
1164
|
+
const prevAnimationState = name && data && data.prevGeneratorState[name];
|
|
1165
|
+
const velocity = prevAnimationState &&
|
|
1166
|
+
(numKeyframes === 1 ||
|
|
1167
|
+
(numKeyframes === 2 && keyframes[0] === null))
|
|
1168
|
+
? prevAnimationState.velocity
|
|
1169
|
+
: 0;
|
|
1170
|
+
const target = keyframes[numKeyframes - 1];
|
|
1171
|
+
const unresolvedOrigin = numKeyframes === 1 ? null : keyframes[0];
|
|
1172
|
+
const origin = unresolvedOrigin === null
|
|
1173
|
+
? prevAnimationState
|
|
1174
|
+
? prevAnimationState.value
|
|
1175
|
+
: parseFloat(getOrigin())
|
|
1176
|
+
: unresolvedOrigin;
|
|
1177
|
+
generator = getGenerator(origin, target, velocity, name === null || name === void 0 ? void 0 : name.includes("scale"));
|
|
1178
|
+
const keyframesMetadata = getKeyframes(generator);
|
|
1179
|
+
settings = Object.assign(Object.assign({}, keyframesMetadata), { easing: "linear" });
|
|
1180
|
+
}
|
|
1181
|
+
else {
|
|
1182
|
+
generator = getGenerator(0, 100);
|
|
1183
|
+
const keyframesMetadata = getKeyframes(generator);
|
|
1184
|
+
settings = {
|
|
1185
|
+
easing: "ease",
|
|
1186
|
+
duration: keyframesMetadata.overshootDuration,
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
// TODO Add test for this
|
|
1190
|
+
if (generator && data && name) {
|
|
1191
|
+
data.generators[name] = generator;
|
|
1192
|
+
}
|
|
1193
|
+
return settings;
|
|
1194
|
+
},
|
|
1195
|
+
};
|
|
1099
1196
|
};
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1197
|
+
}
|
|
1198
|
+
const isNumberOrNull = (value) => typeof value !== "string";
|
|
1199
|
+
|
|
1200
|
+
const spring = createGeneratorEasing(createSpringGenerator);
|
|
1201
|
+
|
|
1202
|
+
const createGlideGenerator = ({ from = 0, velocity = 0.0, power = 0.8, decay = 0.325, bounceDamping, bounceStiffness, changeTarget, min, max, restDistance = 0.5, restSpeed, }) => {
|
|
1203
|
+
decay = ms(decay);
|
|
1204
|
+
const state = {
|
|
1205
|
+
value: from,
|
|
1206
|
+
target: from,
|
|
1207
|
+
velocity,
|
|
1208
|
+
hasReachedTarget: false,
|
|
1209
|
+
done: false,
|
|
1210
|
+
};
|
|
1211
|
+
const isOutOfBounds = (v) => (min !== undefined && v < min) || (max !== undefined && v > max);
|
|
1212
|
+
const nearestBoundary = (v) => {
|
|
1213
|
+
if (min === undefined)
|
|
1214
|
+
return max;
|
|
1215
|
+
if (max === undefined)
|
|
1216
|
+
return min;
|
|
1217
|
+
return Math.abs(min - v) < Math.abs(max - v) ? min : max;
|
|
1218
|
+
};
|
|
1219
|
+
let amplitude = power * velocity;
|
|
1220
|
+
const ideal = from + amplitude;
|
|
1221
|
+
const target = changeTarget === undefined ? ideal : changeTarget(ideal);
|
|
1222
|
+
state.target = target;
|
|
1223
|
+
/**
|
|
1224
|
+
* If the target has changed we need to re-calculate the amplitude, otherwise
|
|
1225
|
+
* the animation will start from the wrong position.
|
|
1226
|
+
*/
|
|
1227
|
+
if (target !== ideal)
|
|
1228
|
+
amplitude = target - from;
|
|
1229
|
+
const calcDelta = (t) => -amplitude * Math.exp(-t / decay);
|
|
1230
|
+
const calcLatest = (t) => target + calcDelta(t);
|
|
1231
|
+
const applyFriction = (t) => {
|
|
1232
|
+
const delta = calcDelta(t);
|
|
1233
|
+
const latest = calcLatest(t);
|
|
1234
|
+
state.done = Math.abs(delta) <= restDistance;
|
|
1235
|
+
state.value = state.done ? target : latest;
|
|
1236
|
+
state.velocity =
|
|
1237
|
+
t === 0 ? velocity : calcVelocity(calcLatest, t, state.value);
|
|
1105
1238
|
};
|
|
1239
|
+
/**
|
|
1240
|
+
* Ideally this would resolve for t in a stateless way, we could
|
|
1241
|
+
* do that by always precalculating the animation but as we know
|
|
1242
|
+
* this will be done anyway we can assume that spring will
|
|
1243
|
+
* be discovered during that.
|
|
1244
|
+
*/
|
|
1245
|
+
let timeReachedBoundary;
|
|
1246
|
+
let spring;
|
|
1247
|
+
const checkCatchBoundary = (t) => {
|
|
1248
|
+
if (!isOutOfBounds(state.value))
|
|
1249
|
+
return;
|
|
1250
|
+
timeReachedBoundary = t;
|
|
1251
|
+
spring = createSpringGenerator({
|
|
1252
|
+
from: state.value,
|
|
1253
|
+
to: nearestBoundary(state.value),
|
|
1254
|
+
velocity: state.velocity,
|
|
1255
|
+
damping: bounceDamping,
|
|
1256
|
+
stiffness: bounceStiffness,
|
|
1257
|
+
restDistance,
|
|
1258
|
+
restSpeed,
|
|
1259
|
+
});
|
|
1260
|
+
};
|
|
1261
|
+
checkCatchBoundary(0);
|
|
1106
1262
|
return {
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
settings = Object.assign(Object.assign({}, keyframesMetadata), { easing: "linear" });
|
|
1263
|
+
next: (t) => {
|
|
1264
|
+
/**
|
|
1265
|
+
* We need to resolve the friction to figure out if we need a
|
|
1266
|
+
* spring but we don't want to do this twice per frame. So here
|
|
1267
|
+
* we flag if we updated for this frame and later if we did
|
|
1268
|
+
* we can skip doing it again.
|
|
1269
|
+
*/
|
|
1270
|
+
let hasUpdatedFrame = false;
|
|
1271
|
+
if (!spring && timeReachedBoundary === undefined) {
|
|
1272
|
+
hasUpdatedFrame = true;
|
|
1273
|
+
applyFriction(t);
|
|
1274
|
+
checkCatchBoundary(t);
|
|
1275
|
+
}
|
|
1276
|
+
/**
|
|
1277
|
+
* If we have a spring and the provided t is beyond the moment the friction
|
|
1278
|
+
* animation crossed the min/max boundary, use the spring.
|
|
1279
|
+
*/
|
|
1280
|
+
if (timeReachedBoundary !== undefined && t > timeReachedBoundary) {
|
|
1281
|
+
state.hasReachedTarget = true;
|
|
1282
|
+
return spring.next(t - timeReachedBoundary);
|
|
1128
1283
|
}
|
|
1129
1284
|
else {
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
easing: "ease",
|
|
1134
|
-
duration: keyframesMetadata.overshootDuration,
|
|
1135
|
-
};
|
|
1285
|
+
state.hasReachedTarget = false;
|
|
1286
|
+
!hasUpdatedFrame && applyFriction(t);
|
|
1287
|
+
return state;
|
|
1136
1288
|
}
|
|
1137
|
-
return settings;
|
|
1138
1289
|
},
|
|
1139
1290
|
};
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
const generator = data.activeGenerators[name];
|
|
1144
|
-
if (animation && generator) {
|
|
1145
|
-
return generator.next(animation.currentTime);
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
const isNumberOrNull = (value) => typeof value !== "string";
|
|
1291
|
+
};
|
|
1292
|
+
|
|
1293
|
+
const glide = createGeneratorEasing(createGlideGenerator);
|
|
1149
1294
|
|
|
1150
1295
|
exports.animate = animate;
|
|
1151
1296
|
exports.animateStyle = animateStyle;
|
|
1297
|
+
exports.glide = glide;
|
|
1152
1298
|
exports.spring = spring;
|
|
1153
1299
|
exports.stagger = stagger;
|
|
1154
1300
|
exports.timeline = timeline;
|