framer-motion 10.2.3 → 10.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/dom-entry.js +1 -1
- package/dist/cjs/index.js +18 -20
- package/dist/cjs/{wrap-27fda06a.js → wrap-62da7859.js} +456 -437
- package/dist/dom-entry.d.ts +485 -37
- package/dist/es/animation/GroupPlaybackControls.mjs +25 -0
- package/dist/es/animation/animate.mjs +2 -3
- package/dist/es/animation/create-instant-animation.mjs +13 -3
- package/dist/es/animation/generators/inertia.mjs +87 -0
- package/dist/es/animation/{legacy-popmotion → generators}/keyframes.mjs +8 -15
- package/dist/es/animation/{legacy-popmotion/find-spring.mjs → generators/spring/find.mjs} +6 -5
- package/dist/es/animation/generators/spring/index.mjs +129 -0
- package/dist/es/animation/generators/utils/velocity.mjs +9 -0
- package/dist/es/animation/index.mjs +2 -10
- package/dist/es/animation/js/driver-frameloop.mjs +12 -0
- package/dist/es/animation/js/index.mjs +206 -0
- package/dist/es/animation/optimized-appear/handoff.mjs +3 -1
- package/dist/es/animation/waapi/create-accelerated-animation.mjs +16 -10
- package/dist/es/frameloop/index.mjs +3 -4
- package/dist/es/gestures/pan/PanSession.mjs +2 -2
- package/dist/es/index.mjs +2 -3
- package/dist/es/render/utils/motion-values.mjs +1 -1
- package/dist/es/utils/time-conversion.mjs +2 -1
- package/dist/es/value/index.mjs +3 -3
- package/dist/es/value/use-spring.mjs +1 -1
- package/dist/es/value/use-velocity.mjs +4 -6
- package/dist/framer-motion.dev.js +475 -458
- package/dist/framer-motion.js +1 -1
- package/dist/index.d.ts +175 -218
- package/dist/projection.dev.js +5849 -5830
- package/dist/three-entry.d.ts +62 -63
- package/package.json +7 -11
- package/dist/es/animation/legacy-popmotion/decay.mjs +0 -34
- package/dist/es/animation/legacy-popmotion/index.mjs +0 -163
- package/dist/es/animation/legacy-popmotion/inertia.mjs +0 -90
- package/dist/es/animation/legacy-popmotion/spring.mjs +0 -143
- package/dist/es/frameloop/on-next-frame.mjs +0 -12
|
@@ -157,17 +157,6 @@ const numberValueTypes = {
|
|
|
157
157
|
const combineFunctions = (a, b) => (v) => b(a(v));
|
|
158
158
|
const pipe = (...transformers) => transformers.reduce(combineFunctions);
|
|
159
159
|
|
|
160
|
-
/*
|
|
161
|
-
Detect and load appropriate clock setting for the execution environment
|
|
162
|
-
*/
|
|
163
|
-
const defaultTimestep = (1 / 60) * 1000;
|
|
164
|
-
const getCurrentTime = typeof performance !== "undefined"
|
|
165
|
-
? () => performance.now()
|
|
166
|
-
: () => Date.now();
|
|
167
|
-
const onNextFrame = typeof window !== "undefined"
|
|
168
|
-
? (callback) => window.requestAnimationFrame(callback)
|
|
169
|
-
: (callback) => setTimeout(() => callback(getCurrentTime()), defaultTimestep);
|
|
170
|
-
|
|
171
160
|
function createRenderStep(runNextFrame) {
|
|
172
161
|
/**
|
|
173
162
|
* We create and reuse two arrays, one to queue jobs for the current frame
|
|
@@ -296,7 +285,7 @@ const processStep = (stepId) => steps[stepId].process(frameData);
|
|
|
296
285
|
const processFrame = (timestamp) => {
|
|
297
286
|
runNextFrame = false;
|
|
298
287
|
frameData.delta = useDefaultElapsed
|
|
299
|
-
?
|
|
288
|
+
? 1000 / 60
|
|
300
289
|
: Math.max(Math.min(timestamp - frameData.timestamp, maxElapsed$1), 1);
|
|
301
290
|
frameData.timestamp = timestamp;
|
|
302
291
|
isProcessing = true;
|
|
@@ -304,14 +293,14 @@ const processFrame = (timestamp) => {
|
|
|
304
293
|
isProcessing = false;
|
|
305
294
|
if (runNextFrame) {
|
|
306
295
|
useDefaultElapsed = false;
|
|
307
|
-
|
|
296
|
+
requestAnimationFrame(processFrame);
|
|
308
297
|
}
|
|
309
298
|
};
|
|
310
299
|
const startLoop = () => {
|
|
311
300
|
runNextFrame = true;
|
|
312
301
|
useDefaultElapsed = true;
|
|
313
302
|
if (!isProcessing)
|
|
314
|
-
|
|
303
|
+
requestAnimationFrame(processFrame);
|
|
315
304
|
};
|
|
316
305
|
|
|
317
306
|
const noop = (any) => any;
|
|
@@ -415,7 +404,7 @@ class MotionValue {
|
|
|
415
404
|
* This will be replaced by the build step with the latest version number.
|
|
416
405
|
* When MotionValues are provided to motion components, warn if versions are mixed.
|
|
417
406
|
*/
|
|
418
|
-
this.version = "10.2.
|
|
407
|
+
this.version = "10.2.4";
|
|
419
408
|
/**
|
|
420
409
|
* Duration, in milliseconds, since last updating frame.
|
|
421
410
|
*
|
|
@@ -661,7 +650,7 @@ class MotionValue {
|
|
|
661
650
|
this.stop();
|
|
662
651
|
return new Promise((resolve) => {
|
|
663
652
|
this.hasAnimated = true;
|
|
664
|
-
this.animation = startAnimation(resolve)
|
|
653
|
+
this.animation = startAnimation(resolve);
|
|
665
654
|
if (this.events.animationStart) {
|
|
666
655
|
this.events.animationStart.notify();
|
|
667
656
|
}
|
|
@@ -695,7 +684,7 @@ class MotionValue {
|
|
|
695
684
|
return !!this.animation;
|
|
696
685
|
}
|
|
697
686
|
clearAnimation() {
|
|
698
|
-
this.animation
|
|
687
|
+
delete this.animation;
|
|
699
688
|
}
|
|
700
689
|
/**
|
|
701
690
|
* Destroy and clean up subscribers to this `MotionValue`.
|
|
@@ -970,11 +959,74 @@ if (process.env.NODE_ENV !== "production") {
|
|
|
970
959
|
* @return milliseconds - Converted time in milliseconds.
|
|
971
960
|
*/
|
|
972
961
|
const secondsToMilliseconds = (seconds) => seconds * 1000;
|
|
962
|
+
const millisecondsToSeconds = (milliseconds) => milliseconds / 1000;
|
|
973
963
|
|
|
974
964
|
const instantAnimationState = {
|
|
975
965
|
current: false,
|
|
976
966
|
};
|
|
977
967
|
|
|
968
|
+
function isWaapiSupportedEasing(easing) {
|
|
969
|
+
return (!easing || // Default easing
|
|
970
|
+
Array.isArray(easing) || // Bezier curve
|
|
971
|
+
(typeof easing === "string" && supportedWaapiEasing[easing]));
|
|
972
|
+
}
|
|
973
|
+
const cubicBezierAsString = ([a, b, c, d]) => `cubic-bezier(${a}, ${b}, ${c}, ${d})`;
|
|
974
|
+
const supportedWaapiEasing = {
|
|
975
|
+
linear: "linear",
|
|
976
|
+
ease: "ease",
|
|
977
|
+
easeIn: "ease-in",
|
|
978
|
+
easeOut: "ease-out",
|
|
979
|
+
easeInOut: "ease-in-out",
|
|
980
|
+
circIn: cubicBezierAsString([0, 0.65, 0.55, 1]),
|
|
981
|
+
circOut: cubicBezierAsString([0.55, 0, 1, 0.45]),
|
|
982
|
+
backIn: cubicBezierAsString([0.31, 0.01, 0.66, -0.59]),
|
|
983
|
+
backOut: cubicBezierAsString([0.33, 1.53, 0.69, 0.99]),
|
|
984
|
+
};
|
|
985
|
+
function mapEasingToNativeEasing(easing) {
|
|
986
|
+
if (!easing)
|
|
987
|
+
return undefined;
|
|
988
|
+
return Array.isArray(easing)
|
|
989
|
+
? cubicBezierAsString(easing)
|
|
990
|
+
: supportedWaapiEasing[easing];
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
function animateStyle(element, valueName, keyframes, { delay = 0, duration, repeat = 0, repeatType = "loop", ease, times, } = {}) {
|
|
994
|
+
const keyframeOptions = { [valueName]: keyframes };
|
|
995
|
+
if (times)
|
|
996
|
+
keyframeOptions.offset = times;
|
|
997
|
+
return element.animate(keyframeOptions, {
|
|
998
|
+
delay,
|
|
999
|
+
duration,
|
|
1000
|
+
easing: mapEasingToNativeEasing(ease),
|
|
1001
|
+
fill: "both",
|
|
1002
|
+
iterations: repeat + 1,
|
|
1003
|
+
direction: repeatType === "reverse" ? "alternate" : "normal",
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
const featureTests = {
|
|
1008
|
+
waapi: () => Object.hasOwnProperty.call(Element.prototype, "animate"),
|
|
1009
|
+
};
|
|
1010
|
+
const results = {};
|
|
1011
|
+
const supports = {};
|
|
1012
|
+
/**
|
|
1013
|
+
* Generate features tests that cache their results.
|
|
1014
|
+
*/
|
|
1015
|
+
for (const key in featureTests) {
|
|
1016
|
+
supports[key] = () => {
|
|
1017
|
+
if (results[key] === undefined)
|
|
1018
|
+
results[key] = featureTests[key]();
|
|
1019
|
+
return results[key];
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
function getFinalKeyframe(keyframes, { repeat, repeatType = "loop" }) {
|
|
1024
|
+
const index = repeat && repeatType !== "loop" && repeat % 2 === 1
|
|
1025
|
+
? 0
|
|
1026
|
+
: keyframes.length - 1;
|
|
1027
|
+
return keyframes[index];
|
|
1028
|
+
}
|
|
1029
|
+
|
|
978
1030
|
// Accepts an easing function and returns a new one that outputs mirrored values for
|
|
979
1031
|
// the second half of the animation. Turns easeIn into easeInOut.
|
|
980
1032
|
const mirrorEasing = (easing) => (p) => p <= 0.5 ? easing(2 * p) / 2 : (2 - easing(2 * (1 - p))) / 2;
|
|
@@ -1343,8 +1395,7 @@ const isEasingArray = (ease) => {
|
|
|
1343
1395
|
function defaultEasing(values, easing) {
|
|
1344
1396
|
return values.map(() => easing || easeInOut).splice(0, values.length - 1);
|
|
1345
1397
|
}
|
|
1346
|
-
function keyframes({ keyframes: keyframeValues,
|
|
1347
|
-
keyframeValues = [...keyframeValues];
|
|
1398
|
+
function keyframes({ duration = 300, keyframes: keyframeValues, times, ease = "easeInOut", }) {
|
|
1348
1399
|
/**
|
|
1349
1400
|
* Easing functions can be externally defined as strings. Here we convert them
|
|
1350
1401
|
* into actual functions.
|
|
@@ -1369,42 +1420,42 @@ function keyframes({ keyframes: keyframeValues, ease = easeInOut, times, duratio
|
|
|
1369
1420
|
times && times.length === keyframeValues.length
|
|
1370
1421
|
? times
|
|
1371
1422
|
: defaultOffset$1(keyframeValues), duration);
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
});
|
|
1378
|
-
}
|
|
1379
|
-
let interpolator = createInterpolator();
|
|
1423
|
+
const mapTimeToKeyframe = interpolate(absoluteTimes, keyframeValues, {
|
|
1424
|
+
ease: Array.isArray(easingFunctions)
|
|
1425
|
+
? easingFunctions
|
|
1426
|
+
: defaultEasing(keyframeValues, easingFunctions),
|
|
1427
|
+
});
|
|
1380
1428
|
return {
|
|
1429
|
+
calculatedDuration: duration,
|
|
1381
1430
|
next: (t) => {
|
|
1382
|
-
state.value =
|
|
1431
|
+
state.value = mapTimeToKeyframe(t);
|
|
1383
1432
|
state.done = t >= duration;
|
|
1384
1433
|
return state;
|
|
1385
1434
|
},
|
|
1386
|
-
flipTarget: () => {
|
|
1387
|
-
keyframeValues.reverse();
|
|
1388
|
-
interpolator = createInterpolator();
|
|
1389
|
-
},
|
|
1390
1435
|
};
|
|
1391
1436
|
}
|
|
1392
1437
|
|
|
1438
|
+
const velocitySampleDuration = 5; // ms
|
|
1439
|
+
function calcGeneratorVelocity(resolveValue, t, current) {
|
|
1440
|
+
const prevT = Math.max(t - velocitySampleDuration, 0);
|
|
1441
|
+
return velocityPerSecond(current - resolveValue(prevT), t - prevT);
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1393
1444
|
const safeMin = 0.001;
|
|
1394
1445
|
const minDuration = 0.01;
|
|
1395
|
-
const maxDuration = 10.0;
|
|
1446
|
+
const maxDuration$2 = 10.0;
|
|
1396
1447
|
const minDamping = 0.05;
|
|
1397
1448
|
const maxDamping = 1;
|
|
1398
1449
|
function findSpring({ duration = 800, bounce = 0.25, velocity = 0, mass = 1, }) {
|
|
1399
1450
|
let envelope;
|
|
1400
1451
|
let derivative;
|
|
1401
|
-
exports.warning(duration <= maxDuration
|
|
1452
|
+
exports.warning(duration <= secondsToMilliseconds(maxDuration$2), "Spring duration must be 10 seconds or less");
|
|
1402
1453
|
let dampingRatio = 1 - bounce;
|
|
1403
1454
|
/**
|
|
1404
1455
|
* Restrict dampingRatio and duration to within acceptable ranges.
|
|
1405
1456
|
*/
|
|
1406
1457
|
dampingRatio = clamp(minDamping, maxDamping, dampingRatio);
|
|
1407
|
-
duration = clamp(minDuration, maxDuration, duration
|
|
1458
|
+
duration = clamp(minDuration, maxDuration$2, millisecondsToSeconds(duration));
|
|
1408
1459
|
if (dampingRatio < 1) {
|
|
1409
1460
|
/**
|
|
1410
1461
|
* Underdamped spring
|
|
@@ -1445,7 +1496,7 @@ function findSpring({ duration = 800, bounce = 0.25, velocity = 0, mass = 1, })
|
|
|
1445
1496
|
}
|
|
1446
1497
|
const initialGuess = 5 / duration;
|
|
1447
1498
|
const undampedFreq = approximateRoot(envelope, derivative, initialGuess);
|
|
1448
|
-
duration = duration
|
|
1499
|
+
duration = secondsToMilliseconds(duration);
|
|
1449
1500
|
if (isNaN(undampedFreq)) {
|
|
1450
1501
|
return {
|
|
1451
1502
|
stiffness: 100,
|
|
@@ -1502,78 +1553,71 @@ function getSpringOptions(options) {
|
|
|
1502
1553
|
}
|
|
1503
1554
|
return springOptions;
|
|
1504
1555
|
}
|
|
1505
|
-
const velocitySampleDuration = 5;
|
|
1506
|
-
/**
|
|
1507
|
-
* This is based on the spring implementation of Wobble https://github.com/skevy/wobble
|
|
1508
|
-
*/
|
|
1509
1556
|
function spring({ keyframes, restDelta, restSpeed, ...options }) {
|
|
1510
|
-
|
|
1511
|
-
|
|
1557
|
+
const origin = keyframes[0];
|
|
1558
|
+
const target = keyframes[keyframes.length - 1];
|
|
1512
1559
|
/**
|
|
1513
1560
|
* This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
|
|
1514
1561
|
* to reduce GC during animation.
|
|
1515
1562
|
*/
|
|
1516
1563
|
const state = { done: false, value: origin };
|
|
1517
1564
|
const { stiffness, damping, mass, velocity, duration, isResolvedFromDuration, } = getSpringOptions(options);
|
|
1518
|
-
|
|
1519
|
-
let initialVelocity = velocity ? -(velocity / 1000) : 0.0;
|
|
1565
|
+
const initialVelocity = velocity ? -millisecondsToSeconds(velocity) : 0.0;
|
|
1520
1566
|
const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass));
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
(
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
};
|
|
1573
|
-
}
|
|
1567
|
+
const initialDelta = target - origin;
|
|
1568
|
+
const undampedAngularFreq = millisecondsToSeconds(Math.sqrt(stiffness / mass));
|
|
1569
|
+
/**
|
|
1570
|
+
* If we're working on a granular scale, use smaller defaults for determining
|
|
1571
|
+
* when the spring is finished.
|
|
1572
|
+
*
|
|
1573
|
+
* These defaults have been selected emprically based on what strikes a good
|
|
1574
|
+
* ratio between feeling good and finishing as soon as changes are imperceptible.
|
|
1575
|
+
*/
|
|
1576
|
+
const isGranularScale = Math.abs(initialDelta) < 5;
|
|
1577
|
+
restSpeed || (restSpeed = isGranularScale ? 0.01 : 2);
|
|
1578
|
+
restDelta || (restDelta = isGranularScale ? 0.005 : 0.5);
|
|
1579
|
+
let resolveSpring;
|
|
1580
|
+
if (dampingRatio < 1) {
|
|
1581
|
+
const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
|
|
1582
|
+
// Underdamped spring
|
|
1583
|
+
resolveSpring = (t) => {
|
|
1584
|
+
const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
|
|
1585
|
+
return (target -
|
|
1586
|
+
envelope *
|
|
1587
|
+
(((initialVelocity +
|
|
1588
|
+
dampingRatio * undampedAngularFreq * initialDelta) /
|
|
1589
|
+
angularFreq) *
|
|
1590
|
+
Math.sin(angularFreq * t) +
|
|
1591
|
+
initialDelta * Math.cos(angularFreq * t)));
|
|
1592
|
+
};
|
|
1593
|
+
}
|
|
1594
|
+
else if (dampingRatio === 1) {
|
|
1595
|
+
// Critically damped spring
|
|
1596
|
+
resolveSpring = (t) => target -
|
|
1597
|
+
Math.exp(-undampedAngularFreq * t) *
|
|
1598
|
+
(initialDelta +
|
|
1599
|
+
(initialVelocity + undampedAngularFreq * initialDelta) * t);
|
|
1600
|
+
}
|
|
1601
|
+
else {
|
|
1602
|
+
// Overdamped spring
|
|
1603
|
+
const dampedAngularFreq = undampedAngularFreq * Math.sqrt(dampingRatio * dampingRatio - 1);
|
|
1604
|
+
resolveSpring = (t) => {
|
|
1605
|
+
const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
|
|
1606
|
+
// When performing sinh or cosh values can hit Infinity so we cap them here
|
|
1607
|
+
const freqForT = Math.min(dampedAngularFreq * t, 300);
|
|
1608
|
+
return (target -
|
|
1609
|
+
(envelope *
|
|
1610
|
+
((initialVelocity +
|
|
1611
|
+
dampingRatio * undampedAngularFreq * initialDelta) *
|
|
1612
|
+
Math.sinh(freqForT) +
|
|
1613
|
+
dampedAngularFreq *
|
|
1614
|
+
initialDelta *
|
|
1615
|
+
Math.cosh(freqForT))) /
|
|
1616
|
+
dampedAngularFreq);
|
|
1617
|
+
};
|
|
1574
1618
|
}
|
|
1575
|
-
createSpring();
|
|
1576
1619
|
return {
|
|
1620
|
+
calculatedDuration: isResolvedFromDuration ? duration || null : null,
|
|
1577
1621
|
next: (t) => {
|
|
1578
1622
|
const current = resolveSpring(t);
|
|
1579
1623
|
if (!isResolvedFromDuration) {
|
|
@@ -1585,8 +1629,7 @@ function spring({ keyframes, restDelta, restSpeed, ...options }) {
|
|
|
1585
1629
|
* checking only for displacement is enough.
|
|
1586
1630
|
*/
|
|
1587
1631
|
if (dampingRatio < 1) {
|
|
1588
|
-
|
|
1589
|
-
currentVelocity = velocityPerSecond(current - resolveSpring(prevT), t - prevT);
|
|
1632
|
+
currentVelocity = calcGeneratorVelocity(resolveSpring, t, current);
|
|
1590
1633
|
}
|
|
1591
1634
|
else {
|
|
1592
1635
|
currentVelocity = 0;
|
|
@@ -1603,29 +1646,23 @@ function spring({ keyframes, restDelta, restSpeed, ...options }) {
|
|
|
1603
1646
|
state.value = state.done ? target : current;
|
|
1604
1647
|
return state;
|
|
1605
1648
|
},
|
|
1606
|
-
flipTarget: () => {
|
|
1607
|
-
initialVelocity = -initialVelocity;
|
|
1608
|
-
[origin, target] = [target, origin];
|
|
1609
|
-
createSpring();
|
|
1610
|
-
},
|
|
1611
1649
|
};
|
|
1612
1650
|
}
|
|
1613
|
-
spring.needsInterpolation = (a, b) => typeof a === "string" || typeof b === "string";
|
|
1614
|
-
const zero = (_t) => 0;
|
|
1615
1651
|
|
|
1616
|
-
function
|
|
1617
|
-
/**
|
|
1618
|
-
* The decay animation dynamically calculates an end of the animation
|
|
1619
|
-
* based on the initial keyframe, so we only need to define a single keyframe
|
|
1620
|
-
* as default.
|
|
1621
|
-
*/
|
|
1622
|
-
keyframes = [0], velocity = 0, power = 0.8, timeConstant = 350, restDelta = 0.5, modifyTarget, }) {
|
|
1652
|
+
function inertia({ keyframes, velocity = 0.0, power = 0.8, timeConstant = 325, bounceDamping = 10, bounceStiffness = 500, modifyTarget, min, max, restDelta = 0.5, restSpeed, }) {
|
|
1623
1653
|
const origin = keyframes[0];
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
const
|
|
1654
|
+
const state = {
|
|
1655
|
+
done: false,
|
|
1656
|
+
value: origin,
|
|
1657
|
+
};
|
|
1658
|
+
const isOutOfBounds = (v) => (min !== undefined && v < min) || (max !== undefined && v > max);
|
|
1659
|
+
const nearestBoundary = (v) => {
|
|
1660
|
+
if (min === undefined)
|
|
1661
|
+
return max;
|
|
1662
|
+
if (max === undefined)
|
|
1663
|
+
return min;
|
|
1664
|
+
return Math.abs(min - v) < Math.abs(max - v) ? min : max;
|
|
1665
|
+
};
|
|
1629
1666
|
let amplitude = power * velocity;
|
|
1630
1667
|
const ideal = origin + amplitude;
|
|
1631
1668
|
const target = modifyTarget === undefined ? ideal : modifyTarget(ideal);
|
|
@@ -1635,233 +1672,270 @@ keyframes = [0], velocity = 0, power = 0.8, timeConstant = 350, restDelta = 0.5,
|
|
|
1635
1672
|
*/
|
|
1636
1673
|
if (target !== ideal)
|
|
1637
1674
|
amplitude = target - origin;
|
|
1675
|
+
const calcDelta = (t) => -amplitude * Math.exp(-t / timeConstant);
|
|
1676
|
+
const calcLatest = (t) => target + calcDelta(t);
|
|
1677
|
+
const applyFriction = (t) => {
|
|
1678
|
+
const delta = calcDelta(t);
|
|
1679
|
+
const latest = calcLatest(t);
|
|
1680
|
+
state.done = Math.abs(delta) <= restDelta;
|
|
1681
|
+
state.value = state.done ? target : latest;
|
|
1682
|
+
};
|
|
1683
|
+
/**
|
|
1684
|
+
* Ideally this would resolve for t in a stateless way, we could
|
|
1685
|
+
* do that by always precalculating the animation but as we know
|
|
1686
|
+
* this will be done anyway we can assume that spring will
|
|
1687
|
+
* be discovered during that.
|
|
1688
|
+
*/
|
|
1689
|
+
let timeReachedBoundary;
|
|
1690
|
+
let spring$1;
|
|
1691
|
+
const checkCatchBoundary = (t) => {
|
|
1692
|
+
if (!isOutOfBounds(state.value))
|
|
1693
|
+
return;
|
|
1694
|
+
timeReachedBoundary = t;
|
|
1695
|
+
spring$1 = spring({
|
|
1696
|
+
keyframes: [state.value, nearestBoundary(state.value)],
|
|
1697
|
+
velocity: calcGeneratorVelocity(calcLatest, t, state.value),
|
|
1698
|
+
damping: bounceDamping,
|
|
1699
|
+
stiffness: bounceStiffness,
|
|
1700
|
+
restDelta,
|
|
1701
|
+
restSpeed,
|
|
1702
|
+
});
|
|
1703
|
+
};
|
|
1704
|
+
checkCatchBoundary(0);
|
|
1638
1705
|
return {
|
|
1706
|
+
calculatedDuration: null,
|
|
1639
1707
|
next: (t) => {
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1708
|
+
/**
|
|
1709
|
+
* We need to resolve the friction to figure out if we need a
|
|
1710
|
+
* spring but we don't want to do this twice per frame. So here
|
|
1711
|
+
* we flag if we updated for this frame and later if we did
|
|
1712
|
+
* we can skip doing it again.
|
|
1713
|
+
*/
|
|
1714
|
+
let hasUpdatedFrame = false;
|
|
1715
|
+
if (!spring$1 && timeReachedBoundary === undefined) {
|
|
1716
|
+
hasUpdatedFrame = true;
|
|
1717
|
+
applyFriction(t);
|
|
1718
|
+
checkCatchBoundary(t);
|
|
1719
|
+
}
|
|
1720
|
+
/**
|
|
1721
|
+
* If we have a spring and the provided t is beyond the moment the friction
|
|
1722
|
+
* animation crossed the min/max boundary, use the spring.
|
|
1723
|
+
*/
|
|
1724
|
+
if (timeReachedBoundary !== undefined && t > timeReachedBoundary) {
|
|
1725
|
+
return spring$1.next(t - timeReachedBoundary);
|
|
1726
|
+
}
|
|
1727
|
+
else {
|
|
1728
|
+
!hasUpdatedFrame && applyFriction(t);
|
|
1729
|
+
return state;
|
|
1730
|
+
}
|
|
1644
1731
|
},
|
|
1645
|
-
flipTarget: () => { },
|
|
1646
1732
|
};
|
|
1647
1733
|
}
|
|
1648
1734
|
|
|
1649
|
-
const
|
|
1650
|
-
|
|
1651
|
-
keyframes: keyframes,
|
|
1652
|
-
tween: keyframes,
|
|
1653
|
-
spring,
|
|
1654
|
-
};
|
|
1655
|
-
function loopElapsed(elapsed, duration, delay = 0) {
|
|
1656
|
-
return elapsed - duration - delay;
|
|
1657
|
-
}
|
|
1658
|
-
function reverseElapsed(elapsed, duration = 0, delay = 0, isForwardPlayback = true) {
|
|
1659
|
-
return isForwardPlayback
|
|
1660
|
-
? loopElapsed(duration + -elapsed, duration, delay)
|
|
1661
|
-
: duration - (elapsed - duration) + delay;
|
|
1662
|
-
}
|
|
1663
|
-
function hasRepeatDelayElapsed(elapsed, duration, delay, isForwardPlayback) {
|
|
1664
|
-
return isForwardPlayback ? elapsed >= duration + delay : elapsed <= -delay;
|
|
1665
|
-
}
|
|
1666
|
-
const framesync = (update) => {
|
|
1667
|
-
const passTimestamp = ({ delta }) => update(delta);
|
|
1735
|
+
const frameloopDriver = (update) => {
|
|
1736
|
+
const passTimestamp = ({ timestamp }) => update(timestamp);
|
|
1668
1737
|
return {
|
|
1669
1738
|
start: () => sync.update(passTimestamp, true),
|
|
1670
1739
|
stop: () => cancelSync.update(passTimestamp),
|
|
1740
|
+
now: () => performance.now(),
|
|
1671
1741
|
};
|
|
1672
1742
|
};
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1743
|
+
|
|
1744
|
+
const types = {
|
|
1745
|
+
decay: inertia,
|
|
1746
|
+
inertia,
|
|
1747
|
+
tween: keyframes,
|
|
1748
|
+
keyframes: keyframes,
|
|
1749
|
+
spring,
|
|
1750
|
+
};
|
|
1751
|
+
/**
|
|
1752
|
+
* Implement a practical max duration for keyframe generation
|
|
1753
|
+
* to prevent infinite loops
|
|
1754
|
+
*/
|
|
1755
|
+
const maxDuration$1 = 20000;
|
|
1756
|
+
function calculateDuration(generator) {
|
|
1757
|
+
let duration = 0;
|
|
1758
|
+
const timeStep = 50;
|
|
1759
|
+
let state = generator.next(duration);
|
|
1760
|
+
while (!state.done && duration < maxDuration$1) {
|
|
1761
|
+
duration += timeStep;
|
|
1762
|
+
state = generator.next(duration);
|
|
1763
|
+
}
|
|
1764
|
+
return duration;
|
|
1765
|
+
}
|
|
1766
|
+
function animateValue({ autoplay = true, delay = 0, driver = frameloopDriver, keyframes: keyframes$1, type = "keyframes", repeat = 0, repeatDelay = 0, repeatType = "loop", onPlay, onStop, onComplete, onUpdate, ...options }) {
|
|
1767
|
+
let animationDriver;
|
|
1768
|
+
const generatorFactory = types[type] || keyframes;
|
|
1685
1769
|
/**
|
|
1686
|
-
* If this
|
|
1687
|
-
*
|
|
1770
|
+
* If this isn't the keyframes generator and we've been provided
|
|
1771
|
+
* strings as keyframes, we need to interpolate these.
|
|
1772
|
+
* TODO: Support velocity for units and complex value types/
|
|
1688
1773
|
*/
|
|
1689
|
-
|
|
1690
|
-
if (
|
|
1691
|
-
|
|
1774
|
+
let mapNumbersToKeyframes;
|
|
1775
|
+
if (generatorFactory !== keyframes &&
|
|
1776
|
+
typeof keyframes$1[0] !== "number") {
|
|
1777
|
+
mapNumbersToKeyframes = interpolate([0, 100], keyframes$1, {
|
|
1692
1778
|
clamp: false,
|
|
1693
1779
|
});
|
|
1694
1780
|
keyframes$1 = [0, 100];
|
|
1695
1781
|
}
|
|
1696
|
-
const
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1782
|
+
const generator = generatorFactory({ ...options, keyframes: keyframes$1 });
|
|
1783
|
+
let mirroredGenerator;
|
|
1784
|
+
if (repeatType === "mirror") {
|
|
1785
|
+
mirroredGenerator = generatorFactory({
|
|
1786
|
+
...options,
|
|
1787
|
+
keyframes: [...keyframes$1].reverse(),
|
|
1788
|
+
velocity: -(options.velocity || 0),
|
|
1789
|
+
});
|
|
1790
|
+
}
|
|
1791
|
+
let playState = "idle";
|
|
1792
|
+
let holdTime = null;
|
|
1793
|
+
let startTime = null;
|
|
1794
|
+
/**
|
|
1795
|
+
* If duration is undefined and we have repeat options,
|
|
1796
|
+
* we need to calculate a duration from the generator.
|
|
1797
|
+
*
|
|
1798
|
+
* We set it to the generator itself to cache the duration.
|
|
1799
|
+
* Any timeline resolver will need to have already precalculated
|
|
1800
|
+
* the duration by this step.
|
|
1801
|
+
*/
|
|
1802
|
+
if (generator.calculatedDuration === null && repeat) {
|
|
1803
|
+
generator.calculatedDuration = calculateDuration(generator);
|
|
1804
|
+
}
|
|
1805
|
+
const { calculatedDuration } = generator;
|
|
1806
|
+
let resolvedDuration = Infinity;
|
|
1807
|
+
let totalDuration = Infinity;
|
|
1808
|
+
if (calculatedDuration) {
|
|
1809
|
+
resolvedDuration = calculatedDuration + repeatDelay;
|
|
1810
|
+
totalDuration = resolvedDuration * (repeat + 1) - repeatDelay;
|
|
1811
|
+
}
|
|
1812
|
+
let currentTime = 0;
|
|
1813
|
+
const tick = (timestamp) => {
|
|
1814
|
+
if (startTime === null)
|
|
1815
|
+
return;
|
|
1816
|
+
if (holdTime !== null) {
|
|
1817
|
+
currentTime = holdTime;
|
|
1706
1818
|
}
|
|
1707
1819
|
else {
|
|
1708
|
-
|
|
1709
|
-
if (repeatType === "mirror")
|
|
1710
|
-
animation.flipTarget();
|
|
1820
|
+
currentTime = timestamp - startTime;
|
|
1711
1821
|
}
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
if (!isForwardPlayback)
|
|
1721
|
-
delta = -delta;
|
|
1722
|
-
elapsed += delta;
|
|
1723
|
-
if (!isComplete) {
|
|
1724
|
-
state = animation.next(Math.max(0, elapsed));
|
|
1725
|
-
if (interpolateFromNumber)
|
|
1726
|
-
state.value = interpolateFromNumber(state.value);
|
|
1727
|
-
isComplete = isForwardPlayback ? state.done : elapsed <= 0;
|
|
1822
|
+
// Rebase on delay
|
|
1823
|
+
currentTime = Math.max(currentTime - delay, 0);
|
|
1824
|
+
/**
|
|
1825
|
+
* If this animation has finished, set the current time
|
|
1826
|
+
* to the total duration.
|
|
1827
|
+
*/
|
|
1828
|
+
if (playState === "finished" && holdTime === null) {
|
|
1829
|
+
currentTime = totalDuration;
|
|
1728
1830
|
}
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1831
|
+
let elapsed = currentTime;
|
|
1832
|
+
let frameGenerator = generator;
|
|
1833
|
+
if (repeat) {
|
|
1834
|
+
/**
|
|
1835
|
+
* Get the current progress (0-1) of the animation. If t is >
|
|
1836
|
+
* than duration we'll get values like 2.5 (midway through the
|
|
1837
|
+
* third iteration)
|
|
1838
|
+
*/
|
|
1839
|
+
const progress = currentTime / resolvedDuration;
|
|
1840
|
+
/**
|
|
1841
|
+
* Get the current iteration (0 indexed). For instance the floor of
|
|
1842
|
+
* 2.5 is 2.
|
|
1843
|
+
*/
|
|
1844
|
+
let currentIteration = Math.floor(progress);
|
|
1845
|
+
/**
|
|
1846
|
+
* Get the current progress of the iteration by taking the remainder
|
|
1847
|
+
* so 2.5 is 0.5 through iteration 2
|
|
1848
|
+
*/
|
|
1849
|
+
let iterationProgress = progress % 1.0;
|
|
1850
|
+
/**
|
|
1851
|
+
* If iteration progress is 1 we count that as the end
|
|
1852
|
+
* of the previous iteration.
|
|
1853
|
+
*/
|
|
1854
|
+
if (!iterationProgress && progress >= 1) {
|
|
1855
|
+
iterationProgress = 1;
|
|
1737
1856
|
}
|
|
1738
|
-
|
|
1739
|
-
|
|
1857
|
+
iterationProgress === 1 && currentIteration--;
|
|
1858
|
+
/**
|
|
1859
|
+
* Reverse progress if we're not running in "normal" direction
|
|
1860
|
+
*/
|
|
1861
|
+
const iterationIsOdd = currentIteration % 2;
|
|
1862
|
+
if (iterationIsOdd) {
|
|
1863
|
+
if (repeatType === "reverse") {
|
|
1864
|
+
iterationProgress = 1 - iterationProgress;
|
|
1865
|
+
if (repeatDelay) {
|
|
1866
|
+
iterationProgress -= repeatDelay / resolvedDuration;
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
else if (repeatType === "mirror") {
|
|
1870
|
+
frameGenerator = mirroredGenerator;
|
|
1871
|
+
}
|
|
1740
1872
|
}
|
|
1873
|
+
const p = currentTime >= totalDuration
|
|
1874
|
+
? repeatType === "reverse" && iterationIsOdd
|
|
1875
|
+
? 0
|
|
1876
|
+
: 1
|
|
1877
|
+
: clamp(0, 1, iterationProgress);
|
|
1878
|
+
elapsed = p * resolvedDuration;
|
|
1741
1879
|
}
|
|
1742
|
-
|
|
1743
|
-
|
|
1880
|
+
const state = frameGenerator.next(elapsed);
|
|
1881
|
+
let { value, done } = state;
|
|
1882
|
+
if (onUpdate) {
|
|
1883
|
+
onUpdate(mapNumbersToKeyframes ? mapNumbersToKeyframes(value) : value);
|
|
1884
|
+
}
|
|
1885
|
+
if (calculatedDuration !== null) {
|
|
1886
|
+
done = currentTime >= totalDuration;
|
|
1887
|
+
}
|
|
1888
|
+
const isAnimationFinished = holdTime === null &&
|
|
1889
|
+
(playState === "finished" || (playState === "running" && done));
|
|
1890
|
+
if (isAnimationFinished) {
|
|
1891
|
+
playState = "finished";
|
|
1892
|
+
onComplete && onComplete();
|
|
1893
|
+
animationDriver && animationDriver.stop();
|
|
1894
|
+
}
|
|
1895
|
+
return state;
|
|
1896
|
+
};
|
|
1897
|
+
const play = () => {
|
|
1898
|
+
animationDriver = driver(tick);
|
|
1899
|
+
const now = animationDriver.now();
|
|
1744
1900
|
onPlay && onPlay();
|
|
1745
|
-
|
|
1746
|
-
|
|
1901
|
+
playState = "running";
|
|
1902
|
+
if (holdTime !== null) {
|
|
1903
|
+
startTime = now - holdTime;
|
|
1904
|
+
}
|
|
1905
|
+
else if (!startTime) {
|
|
1906
|
+
// TODO When implementing play/pause, check WAAPI
|
|
1907
|
+
// logic around finished animations
|
|
1908
|
+
startTime = now;
|
|
1909
|
+
}
|
|
1910
|
+
holdTime = null;
|
|
1911
|
+
animationDriver.start();
|
|
1912
|
+
};
|
|
1913
|
+
if (autoplay) {
|
|
1914
|
+
play();
|
|
1747
1915
|
}
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
onStop && onStop();
|
|
1752
|
-
driverControls && driverControls.stop();
|
|
1753
|
-
},
|
|
1754
|
-
/**
|
|
1755
|
-
* Set the current time of the animation. This is purposefully
|
|
1756
|
-
* mirroring the WAAPI animation API to make them interchanagable.
|
|
1757
|
-
* Going forward this file should be ported more towards
|
|
1758
|
-
* https://github.com/motiondivision/motionone/blob/main/packages/animation/src/Animation.ts
|
|
1759
|
-
* Which behaviourally adheres to WAAPI as far as possible.
|
|
1760
|
-
*
|
|
1761
|
-
* WARNING: This is not safe to use for most animations. We currently
|
|
1762
|
-
* only use it for handoff from WAAPI within Framer.
|
|
1763
|
-
*
|
|
1764
|
-
* This animation function consumes time every frame rather than being sampled for time.
|
|
1765
|
-
* So the sample() method performs some headless frames to ensure
|
|
1766
|
-
* repeats are handled correctly. Ideally in the future we will replace
|
|
1767
|
-
* that method with this, once repeat calculations are pure.
|
|
1768
|
-
*/
|
|
1769
|
-
set currentTime(t) {
|
|
1770
|
-
elapsed = initialElapsed;
|
|
1771
|
-
update(t);
|
|
1916
|
+
const controls = {
|
|
1917
|
+
get currentTime() {
|
|
1918
|
+
return millisecondsToSeconds(currentTime);
|
|
1772
1919
|
},
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
* temporal-resolution version.
|
|
1777
|
-
*
|
|
1778
|
-
* isControlled should be set to true if sample is being run within
|
|
1779
|
-
* a loop. This indicates that we're not arbitrarily sampling
|
|
1780
|
-
* the animation but running it one step after another. Therefore
|
|
1781
|
-
* we don't need to run a low-res version here. This is a stop-gap
|
|
1782
|
-
* until a rewrite can sample for time.
|
|
1783
|
-
*/
|
|
1784
|
-
sample: (t, isControlled = false) => {
|
|
1785
|
-
elapsed = initialElapsed;
|
|
1786
|
-
if (isControlled) {
|
|
1787
|
-
update(t);
|
|
1788
|
-
return state;
|
|
1920
|
+
set currentTime(newTime) {
|
|
1921
|
+
if (holdTime !== null || !animationDriver) {
|
|
1922
|
+
holdTime = 0;
|
|
1789
1923
|
}
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
let sampleElapsed = 0;
|
|
1794
|
-
update(0);
|
|
1795
|
-
while (sampleElapsed <= t) {
|
|
1796
|
-
const remaining = t - sampleElapsed;
|
|
1797
|
-
update(Math.min(remaining, sampleResolution));
|
|
1798
|
-
sampleElapsed += sampleResolution;
|
|
1924
|
+
else {
|
|
1925
|
+
startTime =
|
|
1926
|
+
animationDriver.now() - secondsToMilliseconds(newTime);
|
|
1799
1927
|
}
|
|
1800
|
-
|
|
1928
|
+
},
|
|
1929
|
+
stop: () => {
|
|
1930
|
+
onStop && onStop();
|
|
1931
|
+
animationDriver && animationDriver.stop();
|
|
1932
|
+
},
|
|
1933
|
+
sample: (elapsed) => {
|
|
1934
|
+
startTime = 0;
|
|
1935
|
+
return tick(elapsed);
|
|
1801
1936
|
},
|
|
1802
1937
|
};
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
function isWaapiSupportedEasing(easing) {
|
|
1806
|
-
return (!easing || // Default easing
|
|
1807
|
-
Array.isArray(easing) || // Bezier curve
|
|
1808
|
-
(typeof easing === "string" && supportedWaapiEasing[easing]));
|
|
1809
|
-
}
|
|
1810
|
-
const cubicBezierAsString = ([a, b, c, d]) => `cubic-bezier(${a}, ${b}, ${c}, ${d})`;
|
|
1811
|
-
const supportedWaapiEasing = {
|
|
1812
|
-
linear: "linear",
|
|
1813
|
-
ease: "ease",
|
|
1814
|
-
easeIn: "ease-in",
|
|
1815
|
-
easeOut: "ease-out",
|
|
1816
|
-
easeInOut: "ease-in-out",
|
|
1817
|
-
circIn: cubicBezierAsString([0, 0.65, 0.55, 1]),
|
|
1818
|
-
circOut: cubicBezierAsString([0.55, 0, 1, 0.45]),
|
|
1819
|
-
backIn: cubicBezierAsString([0.31, 0.01, 0.66, -0.59]),
|
|
1820
|
-
backOut: cubicBezierAsString([0.33, 1.53, 0.69, 0.99]),
|
|
1821
|
-
};
|
|
1822
|
-
function mapEasingToNativeEasing(easing) {
|
|
1823
|
-
if (!easing)
|
|
1824
|
-
return undefined;
|
|
1825
|
-
return Array.isArray(easing)
|
|
1826
|
-
? cubicBezierAsString(easing)
|
|
1827
|
-
: supportedWaapiEasing[easing];
|
|
1828
|
-
}
|
|
1829
|
-
|
|
1830
|
-
function animateStyle(element, valueName, keyframes, { delay = 0, duration, repeat = 0, repeatType = "loop", ease, times, } = {}) {
|
|
1831
|
-
const keyframeOptions = { [valueName]: keyframes };
|
|
1832
|
-
if (times)
|
|
1833
|
-
keyframeOptions.offset = times;
|
|
1834
|
-
return element.animate(keyframeOptions, {
|
|
1835
|
-
delay,
|
|
1836
|
-
duration,
|
|
1837
|
-
easing: mapEasingToNativeEasing(ease),
|
|
1838
|
-
fill: "both",
|
|
1839
|
-
iterations: repeat + 1,
|
|
1840
|
-
direction: repeatType === "reverse" ? "alternate" : "normal",
|
|
1841
|
-
});
|
|
1842
|
-
}
|
|
1843
|
-
|
|
1844
|
-
const featureTests = {
|
|
1845
|
-
waapi: () => Object.hasOwnProperty.call(Element.prototype, "animate"),
|
|
1846
|
-
};
|
|
1847
|
-
const results = {};
|
|
1848
|
-
const supports = {};
|
|
1849
|
-
/**
|
|
1850
|
-
* Generate features tests that cache their results.
|
|
1851
|
-
*/
|
|
1852
|
-
for (const key in featureTests) {
|
|
1853
|
-
supports[key] = () => {
|
|
1854
|
-
if (results[key] === undefined)
|
|
1855
|
-
results[key] = featureTests[key]();
|
|
1856
|
-
return results[key];
|
|
1857
|
-
};
|
|
1858
|
-
}
|
|
1859
|
-
|
|
1860
|
-
function getFinalKeyframe(keyframes, { repeat, repeatType = "loop" }) {
|
|
1861
|
-
const index = repeat && repeatType !== "loop" && repeat % 2 === 1
|
|
1862
|
-
? 0
|
|
1863
|
-
: keyframes.length - 1;
|
|
1864
|
-
return keyframes[index];
|
|
1938
|
+
return controls;
|
|
1865
1939
|
}
|
|
1866
1940
|
|
|
1867
1941
|
/**
|
|
@@ -1880,6 +1954,11 @@ const acceleratedValues = new Set([
|
|
|
1880
1954
|
* keyframe quantity.
|
|
1881
1955
|
*/
|
|
1882
1956
|
const sampleDelta = 10; //ms
|
|
1957
|
+
/**
|
|
1958
|
+
* Implement a practical max duration for keyframe generation
|
|
1959
|
+
* to prevent infinite loops
|
|
1960
|
+
*/
|
|
1961
|
+
const maxDuration = 20000;
|
|
1883
1962
|
const requiresPregeneratedKeyframes = (valueName, options) => options.type === "spring" ||
|
|
1884
1963
|
valueName === "backgroundColor" ||
|
|
1885
1964
|
!isWaapiSupportedEasing(options.ease);
|
|
@@ -1888,10 +1967,11 @@ function createAcceleratedAnimation(value, valueName, { onUpdate, onComplete, ..
|
|
|
1888
1967
|
acceleratedValues.has(valueName) &&
|
|
1889
1968
|
!options.repeatDelay &&
|
|
1890
1969
|
options.repeatType !== "mirror" &&
|
|
1891
|
-
options.damping !== 0
|
|
1970
|
+
options.damping !== 0 &&
|
|
1971
|
+
options.type !== "inertia";
|
|
1892
1972
|
if (!canAccelerateAnimation)
|
|
1893
1973
|
return false;
|
|
1894
|
-
let { keyframes, duration = 300,
|
|
1974
|
+
let { keyframes, duration = 300, ease } = options;
|
|
1895
1975
|
/**
|
|
1896
1976
|
* If this animation needs pre-generated keyframes then generate.
|
|
1897
1977
|
*/
|
|
@@ -1899,7 +1979,7 @@ function createAcceleratedAnimation(value, valueName, { onUpdate, onComplete, ..
|
|
|
1899
1979
|
const sampleAnimation = animateValue({
|
|
1900
1980
|
...options,
|
|
1901
1981
|
repeat: 0,
|
|
1902
|
-
|
|
1982
|
+
delay: 0,
|
|
1903
1983
|
});
|
|
1904
1984
|
let state = { done: false, value: keyframes[0] };
|
|
1905
1985
|
const pregeneratedKeyframes = [];
|
|
@@ -1908,8 +1988,8 @@ function createAcceleratedAnimation(value, valueName, { onUpdate, onComplete, ..
|
|
|
1908
1988
|
* we're heading for an infinite loop.
|
|
1909
1989
|
*/
|
|
1910
1990
|
let t = 0;
|
|
1911
|
-
while (!state.done && t <
|
|
1912
|
-
state = sampleAnimation.sample(t
|
|
1991
|
+
while (!state.done && t < maxDuration) {
|
|
1992
|
+
state = sampleAnimation.sample(t);
|
|
1913
1993
|
pregeneratedKeyframes.push(state.value);
|
|
1914
1994
|
t += sampleDelta;
|
|
1915
1995
|
}
|
|
@@ -1919,7 +1999,6 @@ function createAcceleratedAnimation(value, valueName, { onUpdate, onComplete, ..
|
|
|
1919
1999
|
}
|
|
1920
2000
|
const animation = animateStyle(value.owner.current, valueName, keyframes, {
|
|
1921
2001
|
...options,
|
|
1922
|
-
delay: -elapsed,
|
|
1923
2002
|
duration,
|
|
1924
2003
|
/**
|
|
1925
2004
|
* This function is currently not called if ease is provided
|
|
@@ -1949,10 +2028,10 @@ function createAcceleratedAnimation(value, valueName, { onUpdate, onComplete, ..
|
|
|
1949
2028
|
*/
|
|
1950
2029
|
return {
|
|
1951
2030
|
get currentTime() {
|
|
1952
|
-
return animation.currentTime || 0;
|
|
2031
|
+
return millisecondsToSeconds(animation.currentTime || 0);
|
|
1953
2032
|
},
|
|
1954
|
-
set currentTime(
|
|
1955
|
-
animation.currentTime =
|
|
2033
|
+
set currentTime(newTime) {
|
|
2034
|
+
animation.currentTime = secondsToMilliseconds(newTime);
|
|
1956
2035
|
},
|
|
1957
2036
|
stop: () => {
|
|
1958
2037
|
/**
|
|
@@ -1976,113 +2055,22 @@ function createAcceleratedAnimation(value, valueName, { onUpdate, onComplete, ..
|
|
|
1976
2055
|
};
|
|
1977
2056
|
}
|
|
1978
2057
|
|
|
1979
|
-
|
|
1980
|
-
* Timeout defined in ms
|
|
1981
|
-
*/
|
|
1982
|
-
function delay(callback, timeout) {
|
|
1983
|
-
const start = performance.now();
|
|
1984
|
-
const checkElapsed = ({ timestamp }) => {
|
|
1985
|
-
const elapsed = timestamp - start;
|
|
1986
|
-
if (elapsed >= timeout) {
|
|
1987
|
-
cancelSync.read(checkElapsed);
|
|
1988
|
-
callback(elapsed - timeout);
|
|
1989
|
-
}
|
|
1990
|
-
};
|
|
1991
|
-
sync.read(checkElapsed, true);
|
|
1992
|
-
return () => cancelSync.read(checkElapsed);
|
|
1993
|
-
}
|
|
1994
|
-
|
|
1995
|
-
function createInstantAnimation({ keyframes, elapsed, onUpdate, onComplete, }) {
|
|
2058
|
+
function createInstantAnimation({ keyframes, delay: delayBy, onUpdate, onComplete, }) {
|
|
1996
2059
|
const setValue = () => {
|
|
1997
2060
|
onUpdate && onUpdate(keyframes[keyframes.length - 1]);
|
|
1998
2061
|
onComplete && onComplete();
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
function inertia({ keyframes, velocity = 0, min, max, power = 0.8, timeConstant = 750, bounceStiffness = 500, bounceDamping = 10, restDelta = 1, modifyTarget, driver, onUpdate, onComplete, onStop, }) {
|
|
2004
|
-
const origin = keyframes[0];
|
|
2005
|
-
let currentAnimation;
|
|
2006
|
-
function isOutOfBounds(v) {
|
|
2007
|
-
return (min !== undefined && v < min) || (max !== undefined && v > max);
|
|
2008
|
-
}
|
|
2009
|
-
function findNearestBoundary(v) {
|
|
2010
|
-
if (min === undefined)
|
|
2011
|
-
return max;
|
|
2012
|
-
if (max === undefined)
|
|
2013
|
-
return min;
|
|
2014
|
-
return Math.abs(min - v) < Math.abs(max - v) ? min : max;
|
|
2015
|
-
}
|
|
2016
|
-
function startAnimation(options) {
|
|
2017
|
-
currentAnimation && currentAnimation.stop();
|
|
2018
|
-
currentAnimation = animateValue({
|
|
2019
|
-
keyframes: [0, 1],
|
|
2020
|
-
velocity: 0,
|
|
2021
|
-
...options,
|
|
2022
|
-
driver,
|
|
2023
|
-
onUpdate: (v) => {
|
|
2024
|
-
onUpdate && onUpdate(v);
|
|
2025
|
-
options.onUpdate && options.onUpdate(v);
|
|
2026
|
-
},
|
|
2027
|
-
onComplete,
|
|
2028
|
-
onStop,
|
|
2029
|
-
});
|
|
2030
|
-
}
|
|
2031
|
-
function startSpring(options) {
|
|
2032
|
-
startAnimation({
|
|
2033
|
-
type: "spring",
|
|
2034
|
-
stiffness: bounceStiffness,
|
|
2035
|
-
damping: bounceDamping,
|
|
2036
|
-
restDelta,
|
|
2037
|
-
...options,
|
|
2038
|
-
});
|
|
2039
|
-
}
|
|
2040
|
-
if (isOutOfBounds(origin)) {
|
|
2041
|
-
// Start the animation with spring if outside the defined boundaries
|
|
2042
|
-
startSpring({
|
|
2043
|
-
velocity,
|
|
2044
|
-
keyframes: [origin, findNearestBoundary(origin)],
|
|
2045
|
-
});
|
|
2046
|
-
}
|
|
2047
|
-
else {
|
|
2048
|
-
/**
|
|
2049
|
-
* Or if the value is out of bounds, simulate the inertia movement
|
|
2050
|
-
* with the decay animation.
|
|
2051
|
-
*
|
|
2052
|
-
* Pre-calculate the target so we can detect if it's out-of-bounds.
|
|
2053
|
-
* If it is, we want to check per frame when to switch to a spring
|
|
2054
|
-
* animation
|
|
2055
|
-
*/
|
|
2056
|
-
let target = power * velocity + origin;
|
|
2057
|
-
if (typeof modifyTarget !== "undefined")
|
|
2058
|
-
target = modifyTarget(target);
|
|
2059
|
-
const boundary = findNearestBoundary(target);
|
|
2060
|
-
const heading = boundary === min ? -1 : 1;
|
|
2061
|
-
let prev;
|
|
2062
|
-
let current;
|
|
2063
|
-
const checkBoundary = (v) => {
|
|
2064
|
-
prev = current;
|
|
2065
|
-
current = v;
|
|
2066
|
-
velocity = velocityPerSecond(v - prev, frameData.delta);
|
|
2067
|
-
if ((heading === 1 && v > boundary) ||
|
|
2068
|
-
(heading === -1 && v < boundary)) {
|
|
2069
|
-
startSpring({ keyframes: [v, boundary], velocity });
|
|
2070
|
-
}
|
|
2062
|
+
return {
|
|
2063
|
+
stop: () => { },
|
|
2064
|
+
currentTime: 0,
|
|
2071
2065
|
};
|
|
2072
|
-
startAnimation({
|
|
2073
|
-
type: "decay",
|
|
2074
|
-
keyframes: [origin, 0],
|
|
2075
|
-
velocity,
|
|
2076
|
-
timeConstant,
|
|
2077
|
-
power,
|
|
2078
|
-
restDelta,
|
|
2079
|
-
modifyTarget,
|
|
2080
|
-
onUpdate: isOutOfBounds(target) ? checkBoundary : undefined,
|
|
2081
|
-
});
|
|
2082
|
-
}
|
|
2083
|
-
return {
|
|
2084
|
-
stop: () => currentAnimation && currentAnimation.stop(),
|
|
2085
2066
|
};
|
|
2067
|
+
return delayBy
|
|
2068
|
+
? animateValue({
|
|
2069
|
+
keyframes: [0, 1],
|
|
2070
|
+
duration: delayBy,
|
|
2071
|
+
onComplete: setValue,
|
|
2072
|
+
})
|
|
2073
|
+
: setValue();
|
|
2086
2074
|
}
|
|
2087
2075
|
|
|
2088
2076
|
const underDampedSpring = {
|
|
@@ -2238,7 +2226,7 @@ const createMotionValueAnimation = (valueName, value, target, transition = {}) =
|
|
|
2238
2226
|
keyframes,
|
|
2239
2227
|
velocity: value.getVelocity(),
|
|
2240
2228
|
...valueTransition,
|
|
2241
|
-
elapsed,
|
|
2229
|
+
delay: -elapsed,
|
|
2242
2230
|
onUpdate: (v) => {
|
|
2243
2231
|
value.set(v);
|
|
2244
2232
|
valueTransition.onUpdate && valueTransition.onUpdate(v);
|
|
@@ -2258,13 +2246,6 @@ const createMotionValueAnimation = (valueName, value, target, transition = {}) =
|
|
|
2258
2246
|
*/
|
|
2259
2247
|
return createInstantAnimation(options);
|
|
2260
2248
|
}
|
|
2261
|
-
else if (valueTransition.type === "inertia") {
|
|
2262
|
-
/**
|
|
2263
|
-
* If this is an inertia animation, we currently don't support pre-generating
|
|
2264
|
-
* keyframes for this as such it must always run on the main thread.
|
|
2265
|
-
*/
|
|
2266
|
-
return inertia(options);
|
|
2267
|
-
}
|
|
2268
2249
|
/**
|
|
2269
2250
|
* If there's no transition defined for this value, we can generate
|
|
2270
2251
|
* unqiue transition settings for this value.
|
|
@@ -2311,6 +2292,30 @@ function distance2D(a, b) {
|
|
|
2311
2292
|
return Math.sqrt(xDelta ** 2 + yDelta ** 2);
|
|
2312
2293
|
}
|
|
2313
2294
|
|
|
2295
|
+
class GroupPlaybackControls {
|
|
2296
|
+
constructor(animations) {
|
|
2297
|
+
this.animations = animations.filter(Boolean);
|
|
2298
|
+
}
|
|
2299
|
+
/**
|
|
2300
|
+
* TODO: Filter out cancelled or stopped animations before returning
|
|
2301
|
+
*/
|
|
2302
|
+
get currentTime() {
|
|
2303
|
+
return this.animations[0].currentTime;
|
|
2304
|
+
}
|
|
2305
|
+
/**
|
|
2306
|
+
* currentTime assignment could reasonably run every frame, so
|
|
2307
|
+
* we iterate using a normal loop to avoid function creation.
|
|
2308
|
+
*/
|
|
2309
|
+
set currentTime(time) {
|
|
2310
|
+
for (let i = 0; i < this.animations.length; i++) {
|
|
2311
|
+
this.animations[i].currentTime = time;
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
stop() {
|
|
2315
|
+
this.animations.forEach((controls) => controls.stop());
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2314
2319
|
/**
|
|
2315
2320
|
* Animate a single value or a `MotionValue`.
|
|
2316
2321
|
*
|
|
@@ -2341,9 +2346,23 @@ function distance2D(a, b) {
|
|
|
2341
2346
|
function animate(from, to, transition = {}) {
|
|
2342
2347
|
const value = isMotionValue(from) ? from : motionValue(from);
|
|
2343
2348
|
value.start(createMotionValueAnimation("", value, to, transition));
|
|
2344
|
-
return
|
|
2345
|
-
|
|
2349
|
+
return value.animation || new GroupPlaybackControls([]);
|
|
2350
|
+
}
|
|
2351
|
+
|
|
2352
|
+
/**
|
|
2353
|
+
* Timeout defined in ms
|
|
2354
|
+
*/
|
|
2355
|
+
function delay(callback, timeout) {
|
|
2356
|
+
const start = performance.now();
|
|
2357
|
+
const checkElapsed = ({ timestamp }) => {
|
|
2358
|
+
const elapsed = timestamp - start;
|
|
2359
|
+
if (elapsed >= timeout) {
|
|
2360
|
+
cancelSync.read(checkElapsed);
|
|
2361
|
+
callback(elapsed - timeout);
|
|
2362
|
+
}
|
|
2346
2363
|
};
|
|
2364
|
+
sync.read(checkElapsed, true);
|
|
2365
|
+
return () => cancelSync.read(checkElapsed);
|
|
2347
2366
|
}
|
|
2348
2367
|
|
|
2349
2368
|
const isCustomValueType = (v) => {
|
|
@@ -2876,10 +2895,10 @@ exports.getAnimatableNone = getAnimatableNone;
|
|
|
2876
2895
|
exports.getDefaultValueType = getDefaultValueType;
|
|
2877
2896
|
exports.getValueTransition = getValueTransition;
|
|
2878
2897
|
exports.inView = inView;
|
|
2879
|
-
exports.inertia = inertia;
|
|
2880
2898
|
exports.instantAnimationState = instantAnimationState;
|
|
2881
2899
|
exports.interpolate = interpolate;
|
|
2882
2900
|
exports.isMotionValue = isMotionValue;
|
|
2901
|
+
exports.millisecondsToSeconds = millisecondsToSeconds;
|
|
2883
2902
|
exports.mix = mix;
|
|
2884
2903
|
exports.motionValue = motionValue;
|
|
2885
2904
|
exports.moveItem = moveItem;
|