framer-motion 8.5.0 → 8.5.2-alpha.1
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/index.js +105 -65
- package/dist/es/animation/legacy-popmotion/spring.mjs +9 -6
- package/dist/es/animation/optimized-appear/handoff.mjs +28 -16
- package/dist/es/animation/optimized-appear/start.mjs +31 -6
- package/dist/es/animation/optimized-appear/store.mjs +3 -0
- package/dist/es/motion/utils/use-visual-element.mjs +1 -1
- package/dist/es/render/utils/animation.mjs +3 -3
- package/dist/es/render/utils/motion-values.mjs +1 -1
- package/dist/es/value/index.mjs +1 -1
- package/dist/framer-motion.dev.js +105 -65
- package/dist/framer-motion.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/projection.dev.js +11 -8
- package/dist/size-rollup-dom-animation-assets.js +1 -1
- package/dist/size-rollup-dom-animation-m.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-m.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/size-webpack-m.js +1 -1
- package/package.json +7 -7
package/dist/cjs/index.js
CHANGED
|
@@ -90,7 +90,7 @@ function useVisualElement(Component, visualState, props, createVisualElement) {
|
|
|
90
90
|
* So if we detect a situtation where optimised appear animations
|
|
91
91
|
* are running, we use useLayoutEffect to trigger animations.
|
|
92
92
|
*/
|
|
93
|
-
const useAnimateChangesEffect = window.
|
|
93
|
+
const useAnimateChangesEffect = window.HandoffAppearAnimations
|
|
94
94
|
? useIsomorphicLayoutEffect
|
|
95
95
|
: React.useEffect;
|
|
96
96
|
useAnimateChangesEffect(() => {
|
|
@@ -2105,7 +2105,7 @@ class MotionValue {
|
|
|
2105
2105
|
* This will be replaced by the build step with the latest version number.
|
|
2106
2106
|
* When MotionValues are provided to motion components, warn if versions are mixed.
|
|
2107
2107
|
*/
|
|
2108
|
-
this.version = "8.5.
|
|
2108
|
+
this.version = "8.5.2-alpha.1";
|
|
2109
2109
|
/**
|
|
2110
2110
|
* Duration, in milliseconds, since last updating frame.
|
|
2111
2111
|
*
|
|
@@ -2799,54 +2799,6 @@ function isWillChangeMotionValue(value) {
|
|
|
2799
2799
|
return Boolean(isMotionValue(value) && value.add);
|
|
2800
2800
|
}
|
|
2801
2801
|
|
|
2802
|
-
const appearStoreId = (id, value) => `${id}: ${value}`;
|
|
2803
|
-
|
|
2804
|
-
function handoffOptimizedAppearAnimation(id, name, value) {
|
|
2805
|
-
const { MotionAppearAnimations } = window;
|
|
2806
|
-
const animationId = appearStoreId(id, transformProps.has(name) ? "transform" : name);
|
|
2807
|
-
const animation = MotionAppearAnimations && MotionAppearAnimations.get(animationId);
|
|
2808
|
-
if (animation) {
|
|
2809
|
-
const sampledTime = performance.now();
|
|
2810
|
-
/**
|
|
2811
|
-
* Resync handoff animation with optimised animation.
|
|
2812
|
-
*
|
|
2813
|
-
* This step would be unnecessary if we triggered animateChanges() in useEffect,
|
|
2814
|
-
* but due to potential hydration errors we currently fire them in useLayoutEffect.
|
|
2815
|
-
*
|
|
2816
|
-
* By the time we're safely ready to cancel the optimised WAAPI animation,
|
|
2817
|
-
* the main thread might have been blocked and desynced the two animations.
|
|
2818
|
-
*
|
|
2819
|
-
* Here, we resync the two animations before the optimised WAAPI animation is cancelled.
|
|
2820
|
-
*/
|
|
2821
|
-
sync.update(() => {
|
|
2822
|
-
if (value.animation) {
|
|
2823
|
-
value.animation.currentTime = performance.now() - sampledTime;
|
|
2824
|
-
}
|
|
2825
|
-
});
|
|
2826
|
-
/**
|
|
2827
|
-
* We allow the animation to persist until the next frame:
|
|
2828
|
-
* 1. So it continues to play until Framer Motion is ready to render
|
|
2829
|
-
* (avoiding a potential flash of the element's original state)
|
|
2830
|
-
* 2. As all independent transforms share a single transform animation, stopping
|
|
2831
|
-
* it synchronously would prevent subsequent transforms from handing off.
|
|
2832
|
-
*/
|
|
2833
|
-
sync.render(() => {
|
|
2834
|
-
MotionAppearAnimations.delete(animationId);
|
|
2835
|
-
/**
|
|
2836
|
-
* Animation.cancel() throws so it needs to be wrapped in a try/catch
|
|
2837
|
-
*/
|
|
2838
|
-
try {
|
|
2839
|
-
animation.cancel();
|
|
2840
|
-
}
|
|
2841
|
-
catch (e) { }
|
|
2842
|
-
});
|
|
2843
|
-
return animation.currentTime || 0;
|
|
2844
|
-
}
|
|
2845
|
-
else {
|
|
2846
|
-
return 0;
|
|
2847
|
-
}
|
|
2848
|
-
}
|
|
2849
|
-
|
|
2850
2802
|
const optimizedAppearDataId = "framerAppearId";
|
|
2851
2803
|
const optimizedAppearDataAttribute = "data-" + camelToDash(optimizedAppearDataId);
|
|
2852
2804
|
|
|
@@ -3384,7 +3336,7 @@ const velocitySampleDuration = 5;
|
|
|
3384
3336
|
/**
|
|
3385
3337
|
* This is based on the spring implementation of Wobble https://github.com/skevy/wobble
|
|
3386
3338
|
*/
|
|
3387
|
-
function spring({ keyframes,
|
|
3339
|
+
function spring({ keyframes, restDelta, restSpeed, ...options }) {
|
|
3388
3340
|
let origin = keyframes[0];
|
|
3389
3341
|
let target = keyframes[keyframes.length - 1];
|
|
3390
3342
|
/**
|
|
@@ -3400,12 +3352,15 @@ function spring({ keyframes, restSpeed = 2, restDelta = 0.01, ...options }) {
|
|
|
3400
3352
|
const initialDelta = target - origin;
|
|
3401
3353
|
const undampedAngularFreq = Math.sqrt(stiffness / mass) / 1000;
|
|
3402
3354
|
/**
|
|
3403
|
-
* If we're working
|
|
3404
|
-
*
|
|
3355
|
+
* If we're working on a granular scale, use smaller defaults for determining
|
|
3356
|
+
* when the spring is finished.
|
|
3357
|
+
*
|
|
3358
|
+
* These defaults have been selected emprically based on what strikes a good
|
|
3359
|
+
* ratio between feeling good and finishing as soon as changes are imperceptible.
|
|
3405
3360
|
*/
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3361
|
+
const isGranularScale = Math.abs(initialDelta) < 5;
|
|
3362
|
+
restSpeed || (restSpeed = isGranularScale ? 0.01 : 2);
|
|
3363
|
+
restDelta || (restDelta = isGranularScale ? 0.005 : 0.5);
|
|
3409
3364
|
if (dampingRatio < 1) {
|
|
3410
3365
|
const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
|
|
3411
3366
|
// Underdamped spring
|
|
@@ -4245,10 +4200,10 @@ function animateTarget(visualElement, definition, { delay = 0, transitionOverrid
|
|
|
4245
4200
|
* If this is the first time a value is being animated, check
|
|
4246
4201
|
* to see if we're handling off from an existing animation.
|
|
4247
4202
|
*/
|
|
4248
|
-
if (!value.hasAnimated) {
|
|
4203
|
+
if (!value.hasAnimated && window.HandoffAppearAnimations) {
|
|
4249
4204
|
const appearId = visualElement.getProps()[optimizedAppearDataAttribute];
|
|
4250
4205
|
if (appearId) {
|
|
4251
|
-
valueTransition.elapsed =
|
|
4206
|
+
valueTransition.elapsed = window.HandoffAppearAnimations(appearId, key, value, sync);
|
|
4252
4207
|
}
|
|
4253
4208
|
}
|
|
4254
4209
|
let animation = value.start(createMotionValueAnimation(key, value, valueTarget, visualElement.shouldReduceMotion && transformProps.has(key)
|
|
@@ -5998,7 +5953,7 @@ function updateMotionValuesFromProps(element, next, prev) {
|
|
|
5998
5953
|
* and warn against mismatches.
|
|
5999
5954
|
*/
|
|
6000
5955
|
if (process.env.NODE_ENV === "development") {
|
|
6001
|
-
warnOnce(nextValue.version === "8.5.
|
|
5956
|
+
warnOnce(nextValue.version === "8.5.2-alpha.1", `Attempting to mix Framer Motion versions ${nextValue.version} with 8.5.2-alpha.1 may not work as expected.`);
|
|
6002
5957
|
}
|
|
6003
5958
|
}
|
|
6004
5959
|
else if (isMotionValue(prevValue)) {
|
|
@@ -9860,14 +9815,99 @@ function useResetProjection() {
|
|
|
9860
9815
|
return reset;
|
|
9861
9816
|
}
|
|
9862
9817
|
|
|
9863
|
-
|
|
9864
|
-
|
|
9818
|
+
const appearStoreId = (id, value) => `${id}: ${value}`;
|
|
9819
|
+
|
|
9820
|
+
const appearAnimationStore = new Map();
|
|
9821
|
+
|
|
9822
|
+
function handoffOptimizedAppearAnimation(id, name, value,
|
|
9823
|
+
/**
|
|
9824
|
+
* This function is loaded via window by startOptimisedAnimation.
|
|
9825
|
+
* By accepting `sync` as an argument, rather than using it via
|
|
9826
|
+
* import, it can be kept out of the first-load Framer bundle,
|
|
9827
|
+
* while also allowing this function to not be included in
|
|
9828
|
+
* Framer Motion bundles where it's not needed.
|
|
9829
|
+
*/
|
|
9830
|
+
sync) {
|
|
9831
|
+
const storeId = appearStoreId(id, transformProps.has(name) ? "transform" : name);
|
|
9832
|
+
const { animation, ready } = appearAnimationStore.get(storeId) || {};
|
|
9833
|
+
if (!animation)
|
|
9834
|
+
return 0;
|
|
9835
|
+
const cancelOptimisedAnimation = () => {
|
|
9836
|
+
appearAnimationStore.delete(storeId);
|
|
9837
|
+
/**
|
|
9838
|
+
* Animation.cancel() throws so it needs to be wrapped in a try/catch
|
|
9839
|
+
*/
|
|
9840
|
+
try {
|
|
9841
|
+
animation.cancel();
|
|
9842
|
+
}
|
|
9843
|
+
catch (e) { }
|
|
9844
|
+
};
|
|
9845
|
+
if (ready) {
|
|
9846
|
+
const sampledTime = performance.now();
|
|
9847
|
+
/**
|
|
9848
|
+
* Resync handoff animation with optimised animation.
|
|
9849
|
+
*
|
|
9850
|
+
* This step would be unnecessary if we triggered animateChanges() in useEffect,
|
|
9851
|
+
* but due to potential hydration errors we currently fire them in useLayoutEffect.
|
|
9852
|
+
*
|
|
9853
|
+
* By the time we're safely ready to cancel the optimised WAAPI animation,
|
|
9854
|
+
* the main thread might have been blocked and desynced the two animations.
|
|
9855
|
+
*
|
|
9856
|
+
* Here, we resync the two animations before the optimised WAAPI animation is cancelled.
|
|
9857
|
+
*/
|
|
9858
|
+
sync.update(() => {
|
|
9859
|
+
if (value.animation) {
|
|
9860
|
+
value.animation.currentTime = performance.now() - sampledTime;
|
|
9861
|
+
}
|
|
9862
|
+
});
|
|
9863
|
+
/**
|
|
9864
|
+
* We allow the animation to persist until the next frame:
|
|
9865
|
+
* 1. So it continues to play until Framer Motion is ready to render
|
|
9866
|
+
* (avoiding a potential flash of the element's original state)
|
|
9867
|
+
* 2. As all independent transforms share a single transform animation, stopping
|
|
9868
|
+
* it synchronously would prevent subsequent transforms from handing off.
|
|
9869
|
+
*/
|
|
9870
|
+
sync.render(cancelOptimisedAnimation);
|
|
9871
|
+
console.log("handing off from", animation.currentTime);
|
|
9872
|
+
return animation.currentTime || 0;
|
|
9873
|
+
}
|
|
9874
|
+
else {
|
|
9875
|
+
cancelOptimisedAnimation();
|
|
9876
|
+
return 0;
|
|
9877
|
+
}
|
|
9878
|
+
}
|
|
9879
|
+
|
|
9880
|
+
function startOptimizedAppearAnimation(element, name, keyframes, options, onReady) {
|
|
9865
9881
|
const id = element.dataset[optimizedAppearDataId];
|
|
9866
|
-
|
|
9867
|
-
|
|
9868
|
-
|
|
9882
|
+
if (!id)
|
|
9883
|
+
return;
|
|
9884
|
+
window.HandoffAppearAnimations = handoffOptimizedAppearAnimation;
|
|
9885
|
+
const storeId = appearStoreId(id, name);
|
|
9886
|
+
/**
|
|
9887
|
+
* Use a dummy animation to detect when Chrome is ready to start
|
|
9888
|
+
* painting the page and hold off from triggering the real animation
|
|
9889
|
+
* until then.
|
|
9890
|
+
*/
|
|
9891
|
+
const readyAnimation = animateStyle(element, name, [keyframes[0], keyframes[0]], { duration: 1 });
|
|
9892
|
+
appearAnimationStore.set(storeId, {
|
|
9893
|
+
animation: readyAnimation,
|
|
9894
|
+
ready: false,
|
|
9895
|
+
});
|
|
9896
|
+
const startAnimation = () => {
|
|
9897
|
+
const animation = animateStyle(element, name, keyframes, options);
|
|
9898
|
+
appearAnimationStore.set(storeId, { animation, ready: true });
|
|
9899
|
+
if (onReady)
|
|
9900
|
+
onReady(animation);
|
|
9901
|
+
};
|
|
9902
|
+
if (readyAnimation.ready) {
|
|
9903
|
+
readyAnimation.ready.then(() => {
|
|
9904
|
+
readyAnimation.cancel();
|
|
9905
|
+
startAnimation();
|
|
9906
|
+
});
|
|
9907
|
+
}
|
|
9908
|
+
else {
|
|
9909
|
+
startAnimation();
|
|
9869
9910
|
}
|
|
9870
|
-
return animation;
|
|
9871
9911
|
}
|
|
9872
9912
|
|
|
9873
9913
|
const createObject = () => ({});
|
|
@@ -33,7 +33,7 @@ const velocitySampleDuration = 5;
|
|
|
33
33
|
/**
|
|
34
34
|
* This is based on the spring implementation of Wobble https://github.com/skevy/wobble
|
|
35
35
|
*/
|
|
36
|
-
function spring({ keyframes,
|
|
36
|
+
function spring({ keyframes, restDelta, restSpeed, ...options }) {
|
|
37
37
|
let origin = keyframes[0];
|
|
38
38
|
let target = keyframes[keyframes.length - 1];
|
|
39
39
|
/**
|
|
@@ -49,12 +49,15 @@ function spring({ keyframes, restSpeed = 2, restDelta = 0.01, ...options }) {
|
|
|
49
49
|
const initialDelta = target - origin;
|
|
50
50
|
const undampedAngularFreq = Math.sqrt(stiffness / mass) / 1000;
|
|
51
51
|
/**
|
|
52
|
-
* If we're working
|
|
53
|
-
*
|
|
52
|
+
* If we're working on a granular scale, use smaller defaults for determining
|
|
53
|
+
* when the spring is finished.
|
|
54
|
+
*
|
|
55
|
+
* These defaults have been selected emprically based on what strikes a good
|
|
56
|
+
* ratio between feeling good and finishing as soon as changes are imperceptible.
|
|
54
57
|
*/
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
+
const isGranularScale = Math.abs(initialDelta) < 5;
|
|
59
|
+
restSpeed || (restSpeed = isGranularScale ? 0.01 : 2);
|
|
60
|
+
restDelta || (restDelta = isGranularScale ? 0.005 : 0.5);
|
|
58
61
|
if (dampingRatio < 1) {
|
|
59
62
|
const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
|
|
60
63
|
// Underdamped spring
|
|
@@ -1,12 +1,31 @@
|
|
|
1
|
-
import { sync } from '../../frameloop/index.mjs';
|
|
2
1
|
import { transformProps } from '../../render/html/utils/transform.mjs';
|
|
2
|
+
import { appearAnimationStore } from './store.mjs';
|
|
3
3
|
import { appearStoreId } from './store-id.mjs';
|
|
4
4
|
|
|
5
|
-
function handoffOptimizedAppearAnimation(id, name, value
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
function handoffOptimizedAppearAnimation(id, name, value,
|
|
6
|
+
/**
|
|
7
|
+
* This function is loaded via window by startOptimisedAnimation.
|
|
8
|
+
* By accepting `sync` as an argument, rather than using it via
|
|
9
|
+
* import, it can be kept out of the first-load Framer bundle,
|
|
10
|
+
* while also allowing this function to not be included in
|
|
11
|
+
* Framer Motion bundles where it's not needed.
|
|
12
|
+
*/
|
|
13
|
+
sync) {
|
|
14
|
+
const storeId = appearStoreId(id, transformProps.has(name) ? "transform" : name);
|
|
15
|
+
const { animation, ready } = appearAnimationStore.get(storeId) || {};
|
|
16
|
+
if (!animation)
|
|
17
|
+
return 0;
|
|
18
|
+
const cancelOptimisedAnimation = () => {
|
|
19
|
+
appearAnimationStore.delete(storeId);
|
|
20
|
+
/**
|
|
21
|
+
* Animation.cancel() throws so it needs to be wrapped in a try/catch
|
|
22
|
+
*/
|
|
23
|
+
try {
|
|
24
|
+
animation.cancel();
|
|
25
|
+
}
|
|
26
|
+
catch (e) { }
|
|
27
|
+
};
|
|
28
|
+
if (ready) {
|
|
10
29
|
const sampledTime = performance.now();
|
|
11
30
|
/**
|
|
12
31
|
* Resync handoff animation with optimised animation.
|
|
@@ -31,19 +50,12 @@ function handoffOptimizedAppearAnimation(id, name, value) {
|
|
|
31
50
|
* 2. As all independent transforms share a single transform animation, stopping
|
|
32
51
|
* it synchronously would prevent subsequent transforms from handing off.
|
|
33
52
|
*/
|
|
34
|
-
sync.render(
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Animation.cancel() throws so it needs to be wrapped in a try/catch
|
|
38
|
-
*/
|
|
39
|
-
try {
|
|
40
|
-
animation.cancel();
|
|
41
|
-
}
|
|
42
|
-
catch (e) { }
|
|
43
|
-
});
|
|
53
|
+
sync.render(cancelOptimisedAnimation);
|
|
54
|
+
console.log("handing off from", animation.currentTime);
|
|
44
55
|
return animation.currentTime || 0;
|
|
45
56
|
}
|
|
46
57
|
else {
|
|
58
|
+
cancelOptimisedAnimation();
|
|
47
59
|
return 0;
|
|
48
60
|
}
|
|
49
61
|
}
|
|
@@ -1,15 +1,40 @@
|
|
|
1
1
|
import { appearStoreId } from './store-id.mjs';
|
|
2
2
|
import { animateStyle } from '../waapi/index.mjs';
|
|
3
3
|
import { optimizedAppearDataId } from './data-id.mjs';
|
|
4
|
+
import { handoffOptimizedAppearAnimation } from './handoff.mjs';
|
|
5
|
+
import { appearAnimationStore } from './store.mjs';
|
|
4
6
|
|
|
5
|
-
function startOptimizedAppearAnimation(element, name, keyframes, options) {
|
|
6
|
-
window.MotionAppearAnimations || (window.MotionAppearAnimations = new Map());
|
|
7
|
+
function startOptimizedAppearAnimation(element, name, keyframes, options, onReady) {
|
|
7
8
|
const id = element.dataset[optimizedAppearDataId];
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
if (!id)
|
|
10
|
+
return;
|
|
11
|
+
window.HandoffAppearAnimations = handoffOptimizedAppearAnimation;
|
|
12
|
+
const storeId = appearStoreId(id, name);
|
|
13
|
+
/**
|
|
14
|
+
* Use a dummy animation to detect when Chrome is ready to start
|
|
15
|
+
* painting the page and hold off from triggering the real animation
|
|
16
|
+
* until then.
|
|
17
|
+
*/
|
|
18
|
+
const readyAnimation = animateStyle(element, name, [keyframes[0], keyframes[0]], { duration: 1 });
|
|
19
|
+
appearAnimationStore.set(storeId, {
|
|
20
|
+
animation: readyAnimation,
|
|
21
|
+
ready: false,
|
|
22
|
+
});
|
|
23
|
+
const startAnimation = () => {
|
|
24
|
+
const animation = animateStyle(element, name, keyframes, options);
|
|
25
|
+
appearAnimationStore.set(storeId, { animation, ready: true });
|
|
26
|
+
if (onReady)
|
|
27
|
+
onReady(animation);
|
|
28
|
+
};
|
|
29
|
+
if (readyAnimation.ready) {
|
|
30
|
+
readyAnimation.ready.then(() => {
|
|
31
|
+
readyAnimation.cancel();
|
|
32
|
+
startAnimation();
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
startAnimation();
|
|
11
37
|
}
|
|
12
|
-
return animation;
|
|
13
38
|
}
|
|
14
39
|
|
|
15
40
|
export { startOptimizedAppearAnimation };
|
|
@@ -41,7 +41,7 @@ function useVisualElement(Component, visualState, props, createVisualElement) {
|
|
|
41
41
|
* So if we detect a situtation where optimised appear animations
|
|
42
42
|
* are running, we use useLayoutEffect to trigger animations.
|
|
43
43
|
*/
|
|
44
|
-
const useAnimateChangesEffect = window.
|
|
44
|
+
const useAnimateChangesEffect = window.HandoffAppearAnimations
|
|
45
45
|
? useIsomorphicLayoutEffect
|
|
46
46
|
: useEffect;
|
|
47
47
|
useAnimateChangesEffect(() => {
|
|
@@ -2,9 +2,9 @@ import { setTarget } from './setters.mjs';
|
|
|
2
2
|
import { resolveVariant } from './resolve-dynamic-variants.mjs';
|
|
3
3
|
import { transformProps } from '../html/utils/transform.mjs';
|
|
4
4
|
import { isWillChangeMotionValue } from '../../value/use-will-change/is.mjs';
|
|
5
|
-
import { handoffOptimizedAppearAnimation } from '../../animation/optimized-appear/handoff.mjs';
|
|
6
5
|
import { optimizedAppearDataAttribute } from '../../animation/optimized-appear/data-id.mjs';
|
|
7
6
|
import { createMotionValueAnimation } from '../../animation/index.mjs';
|
|
7
|
+
import { sync } from '../../frameloop/index.mjs';
|
|
8
8
|
|
|
9
9
|
function animateVisualElement(visualElement, definition, options = {}) {
|
|
10
10
|
visualElement.notify("AnimationStart", definition);
|
|
@@ -88,10 +88,10 @@ function animateTarget(visualElement, definition, { delay = 0, transitionOverrid
|
|
|
88
88
|
* If this is the first time a value is being animated, check
|
|
89
89
|
* to see if we're handling off from an existing animation.
|
|
90
90
|
*/
|
|
91
|
-
if (!value.hasAnimated) {
|
|
91
|
+
if (!value.hasAnimated && window.HandoffAppearAnimations) {
|
|
92
92
|
const appearId = visualElement.getProps()[optimizedAppearDataAttribute];
|
|
93
93
|
if (appearId) {
|
|
94
|
-
valueTransition.elapsed =
|
|
94
|
+
valueTransition.elapsed = window.HandoffAppearAnimations(appearId, key, value, sync);
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
let animation = value.start(createMotionValueAnimation(key, value, valueTarget, visualElement.shouldReduceMotion && transformProps.has(key)
|
|
@@ -22,7 +22,7 @@ function updateMotionValuesFromProps(element, next, prev) {
|
|
|
22
22
|
* and warn against mismatches.
|
|
23
23
|
*/
|
|
24
24
|
if (process.env.NODE_ENV === "development") {
|
|
25
|
-
warnOnce(nextValue.version === "8.5.
|
|
25
|
+
warnOnce(nextValue.version === "8.5.2-alpha.1", `Attempting to mix Framer Motion versions ${nextValue.version} with 8.5.2-alpha.1 may not work as expected.`);
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
else if (isMotionValue(prevValue)) {
|
package/dist/es/value/index.mjs
CHANGED
|
@@ -25,7 +25,7 @@ class MotionValue {
|
|
|
25
25
|
* This will be replaced by the build step with the latest version number.
|
|
26
26
|
* When MotionValues are provided to motion components, warn if versions are mixed.
|
|
27
27
|
*/
|
|
28
|
-
this.version = "8.5.
|
|
28
|
+
this.version = "8.5.2-alpha.1";
|
|
29
29
|
/**
|
|
30
30
|
* Duration, in milliseconds, since last updating frame.
|
|
31
31
|
*
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
* So if we detect a situtation where optimised appear animations
|
|
89
89
|
* are running, we use useLayoutEffect to trigger animations.
|
|
90
90
|
*/
|
|
91
|
-
const useAnimateChangesEffect = window.
|
|
91
|
+
const useAnimateChangesEffect = window.HandoffAppearAnimations
|
|
92
92
|
? useIsomorphicLayoutEffect
|
|
93
93
|
: React.useEffect;
|
|
94
94
|
useAnimateChangesEffect(() => {
|
|
@@ -2103,7 +2103,7 @@
|
|
|
2103
2103
|
* This will be replaced by the build step with the latest version number.
|
|
2104
2104
|
* When MotionValues are provided to motion components, warn if versions are mixed.
|
|
2105
2105
|
*/
|
|
2106
|
-
this.version = "8.5.
|
|
2106
|
+
this.version = "8.5.2-alpha.1";
|
|
2107
2107
|
/**
|
|
2108
2108
|
* Duration, in milliseconds, since last updating frame.
|
|
2109
2109
|
*
|
|
@@ -2797,54 +2797,6 @@
|
|
|
2797
2797
|
return Boolean(isMotionValue(value) && value.add);
|
|
2798
2798
|
}
|
|
2799
2799
|
|
|
2800
|
-
const appearStoreId = (id, value) => `${id}: ${value}`;
|
|
2801
|
-
|
|
2802
|
-
function handoffOptimizedAppearAnimation(id, name, value) {
|
|
2803
|
-
const { MotionAppearAnimations } = window;
|
|
2804
|
-
const animationId = appearStoreId(id, transformProps.has(name) ? "transform" : name);
|
|
2805
|
-
const animation = MotionAppearAnimations && MotionAppearAnimations.get(animationId);
|
|
2806
|
-
if (animation) {
|
|
2807
|
-
const sampledTime = performance.now();
|
|
2808
|
-
/**
|
|
2809
|
-
* Resync handoff animation with optimised animation.
|
|
2810
|
-
*
|
|
2811
|
-
* This step would be unnecessary if we triggered animateChanges() in useEffect,
|
|
2812
|
-
* but due to potential hydration errors we currently fire them in useLayoutEffect.
|
|
2813
|
-
*
|
|
2814
|
-
* By the time we're safely ready to cancel the optimised WAAPI animation,
|
|
2815
|
-
* the main thread might have been blocked and desynced the two animations.
|
|
2816
|
-
*
|
|
2817
|
-
* Here, we resync the two animations before the optimised WAAPI animation is cancelled.
|
|
2818
|
-
*/
|
|
2819
|
-
sync.update(() => {
|
|
2820
|
-
if (value.animation) {
|
|
2821
|
-
value.animation.currentTime = performance.now() - sampledTime;
|
|
2822
|
-
}
|
|
2823
|
-
});
|
|
2824
|
-
/**
|
|
2825
|
-
* We allow the animation to persist until the next frame:
|
|
2826
|
-
* 1. So it continues to play until Framer Motion is ready to render
|
|
2827
|
-
* (avoiding a potential flash of the element's original state)
|
|
2828
|
-
* 2. As all independent transforms share a single transform animation, stopping
|
|
2829
|
-
* it synchronously would prevent subsequent transforms from handing off.
|
|
2830
|
-
*/
|
|
2831
|
-
sync.render(() => {
|
|
2832
|
-
MotionAppearAnimations.delete(animationId);
|
|
2833
|
-
/**
|
|
2834
|
-
* Animation.cancel() throws so it needs to be wrapped in a try/catch
|
|
2835
|
-
*/
|
|
2836
|
-
try {
|
|
2837
|
-
animation.cancel();
|
|
2838
|
-
}
|
|
2839
|
-
catch (e) { }
|
|
2840
|
-
});
|
|
2841
|
-
return animation.currentTime || 0;
|
|
2842
|
-
}
|
|
2843
|
-
else {
|
|
2844
|
-
return 0;
|
|
2845
|
-
}
|
|
2846
|
-
}
|
|
2847
|
-
|
|
2848
2800
|
const optimizedAppearDataId = "framerAppearId";
|
|
2849
2801
|
const optimizedAppearDataAttribute = "data-" + camelToDash(optimizedAppearDataId);
|
|
2850
2802
|
|
|
@@ -3397,7 +3349,7 @@
|
|
|
3397
3349
|
/**
|
|
3398
3350
|
* This is based on the spring implementation of Wobble https://github.com/skevy/wobble
|
|
3399
3351
|
*/
|
|
3400
|
-
function spring({ keyframes,
|
|
3352
|
+
function spring({ keyframes, restDelta, restSpeed, ...options }) {
|
|
3401
3353
|
let origin = keyframes[0];
|
|
3402
3354
|
let target = keyframes[keyframes.length - 1];
|
|
3403
3355
|
/**
|
|
@@ -3413,12 +3365,15 @@
|
|
|
3413
3365
|
const initialDelta = target - origin;
|
|
3414
3366
|
const undampedAngularFreq = Math.sqrt(stiffness / mass) / 1000;
|
|
3415
3367
|
/**
|
|
3416
|
-
* If we're working
|
|
3417
|
-
*
|
|
3368
|
+
* If we're working on a granular scale, use smaller defaults for determining
|
|
3369
|
+
* when the spring is finished.
|
|
3370
|
+
*
|
|
3371
|
+
* These defaults have been selected emprically based on what strikes a good
|
|
3372
|
+
* ratio between feeling good and finishing as soon as changes are imperceptible.
|
|
3418
3373
|
*/
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3374
|
+
const isGranularScale = Math.abs(initialDelta) < 5;
|
|
3375
|
+
restSpeed || (restSpeed = isGranularScale ? 0.01 : 2);
|
|
3376
|
+
restDelta || (restDelta = isGranularScale ? 0.005 : 0.5);
|
|
3422
3377
|
if (dampingRatio < 1) {
|
|
3423
3378
|
const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
|
|
3424
3379
|
// Underdamped spring
|
|
@@ -4258,10 +4213,10 @@
|
|
|
4258
4213
|
* If this is the first time a value is being animated, check
|
|
4259
4214
|
* to see if we're handling off from an existing animation.
|
|
4260
4215
|
*/
|
|
4261
|
-
if (!value.hasAnimated) {
|
|
4216
|
+
if (!value.hasAnimated && window.HandoffAppearAnimations) {
|
|
4262
4217
|
const appearId = visualElement.getProps()[optimizedAppearDataAttribute];
|
|
4263
4218
|
if (appearId) {
|
|
4264
|
-
valueTransition.elapsed =
|
|
4219
|
+
valueTransition.elapsed = window.HandoffAppearAnimations(appearId, key, value, sync);
|
|
4265
4220
|
}
|
|
4266
4221
|
}
|
|
4267
4222
|
let animation = value.start(createMotionValueAnimation(key, value, valueTarget, visualElement.shouldReduceMotion && transformProps.has(key)
|
|
@@ -6011,7 +5966,7 @@
|
|
|
6011
5966
|
* and warn against mismatches.
|
|
6012
5967
|
*/
|
|
6013
5968
|
{
|
|
6014
|
-
warnOnce(nextValue.version === "8.5.
|
|
5969
|
+
warnOnce(nextValue.version === "8.5.2-alpha.1", `Attempting to mix Framer Motion versions ${nextValue.version} with 8.5.2-alpha.1 may not work as expected.`);
|
|
6015
5970
|
}
|
|
6016
5971
|
}
|
|
6017
5972
|
else if (isMotionValue(prevValue)) {
|
|
@@ -10479,14 +10434,99 @@
|
|
|
10479
10434
|
return reset;
|
|
10480
10435
|
}
|
|
10481
10436
|
|
|
10482
|
-
|
|
10483
|
-
|
|
10437
|
+
const appearStoreId = (id, value) => `${id}: ${value}`;
|
|
10438
|
+
|
|
10439
|
+
const appearAnimationStore = new Map();
|
|
10440
|
+
|
|
10441
|
+
function handoffOptimizedAppearAnimation(id, name, value,
|
|
10442
|
+
/**
|
|
10443
|
+
* This function is loaded via window by startOptimisedAnimation.
|
|
10444
|
+
* By accepting `sync` as an argument, rather than using it via
|
|
10445
|
+
* import, it can be kept out of the first-load Framer bundle,
|
|
10446
|
+
* while also allowing this function to not be included in
|
|
10447
|
+
* Framer Motion bundles where it's not needed.
|
|
10448
|
+
*/
|
|
10449
|
+
sync) {
|
|
10450
|
+
const storeId = appearStoreId(id, transformProps.has(name) ? "transform" : name);
|
|
10451
|
+
const { animation, ready } = appearAnimationStore.get(storeId) || {};
|
|
10452
|
+
if (!animation)
|
|
10453
|
+
return 0;
|
|
10454
|
+
const cancelOptimisedAnimation = () => {
|
|
10455
|
+
appearAnimationStore.delete(storeId);
|
|
10456
|
+
/**
|
|
10457
|
+
* Animation.cancel() throws so it needs to be wrapped in a try/catch
|
|
10458
|
+
*/
|
|
10459
|
+
try {
|
|
10460
|
+
animation.cancel();
|
|
10461
|
+
}
|
|
10462
|
+
catch (e) { }
|
|
10463
|
+
};
|
|
10464
|
+
if (ready) {
|
|
10465
|
+
const sampledTime = performance.now();
|
|
10466
|
+
/**
|
|
10467
|
+
* Resync handoff animation with optimised animation.
|
|
10468
|
+
*
|
|
10469
|
+
* This step would be unnecessary if we triggered animateChanges() in useEffect,
|
|
10470
|
+
* but due to potential hydration errors we currently fire them in useLayoutEffect.
|
|
10471
|
+
*
|
|
10472
|
+
* By the time we're safely ready to cancel the optimised WAAPI animation,
|
|
10473
|
+
* the main thread might have been blocked and desynced the two animations.
|
|
10474
|
+
*
|
|
10475
|
+
* Here, we resync the two animations before the optimised WAAPI animation is cancelled.
|
|
10476
|
+
*/
|
|
10477
|
+
sync.update(() => {
|
|
10478
|
+
if (value.animation) {
|
|
10479
|
+
value.animation.currentTime = performance.now() - sampledTime;
|
|
10480
|
+
}
|
|
10481
|
+
});
|
|
10482
|
+
/**
|
|
10483
|
+
* We allow the animation to persist until the next frame:
|
|
10484
|
+
* 1. So it continues to play until Framer Motion is ready to render
|
|
10485
|
+
* (avoiding a potential flash of the element's original state)
|
|
10486
|
+
* 2. As all independent transforms share a single transform animation, stopping
|
|
10487
|
+
* it synchronously would prevent subsequent transforms from handing off.
|
|
10488
|
+
*/
|
|
10489
|
+
sync.render(cancelOptimisedAnimation);
|
|
10490
|
+
console.log("handing off from", animation.currentTime);
|
|
10491
|
+
return animation.currentTime || 0;
|
|
10492
|
+
}
|
|
10493
|
+
else {
|
|
10494
|
+
cancelOptimisedAnimation();
|
|
10495
|
+
return 0;
|
|
10496
|
+
}
|
|
10497
|
+
}
|
|
10498
|
+
|
|
10499
|
+
function startOptimizedAppearAnimation(element, name, keyframes, options, onReady) {
|
|
10484
10500
|
const id = element.dataset[optimizedAppearDataId];
|
|
10485
|
-
|
|
10486
|
-
|
|
10487
|
-
|
|
10501
|
+
if (!id)
|
|
10502
|
+
return;
|
|
10503
|
+
window.HandoffAppearAnimations = handoffOptimizedAppearAnimation;
|
|
10504
|
+
const storeId = appearStoreId(id, name);
|
|
10505
|
+
/**
|
|
10506
|
+
* Use a dummy animation to detect when Chrome is ready to start
|
|
10507
|
+
* painting the page and hold off from triggering the real animation
|
|
10508
|
+
* until then.
|
|
10509
|
+
*/
|
|
10510
|
+
const readyAnimation = animateStyle(element, name, [keyframes[0], keyframes[0]], { duration: 1 });
|
|
10511
|
+
appearAnimationStore.set(storeId, {
|
|
10512
|
+
animation: readyAnimation,
|
|
10513
|
+
ready: false,
|
|
10514
|
+
});
|
|
10515
|
+
const startAnimation = () => {
|
|
10516
|
+
const animation = animateStyle(element, name, keyframes, options);
|
|
10517
|
+
appearAnimationStore.set(storeId, { animation, ready: true });
|
|
10518
|
+
if (onReady)
|
|
10519
|
+
onReady(animation);
|
|
10520
|
+
};
|
|
10521
|
+
if (readyAnimation.ready) {
|
|
10522
|
+
readyAnimation.ready.then(() => {
|
|
10523
|
+
readyAnimation.cancel();
|
|
10524
|
+
startAnimation();
|
|
10525
|
+
});
|
|
10526
|
+
}
|
|
10527
|
+
else {
|
|
10528
|
+
startAnimation();
|
|
10488
10529
|
}
|
|
10489
|
-
return animation;
|
|
10490
10530
|
}
|
|
10491
10531
|
|
|
10492
10532
|
const createObject = () => ({});
|