framer-motion 7.7.3 → 7.8.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 +1966 -1860
- package/dist/es/animation/animate.mjs +2 -2
- package/dist/es/animation/create-accelerated-animation.mjs +8 -0
- package/dist/es/animation/create-instant-animation.mjs +12 -0
- package/dist/es/animation/{animation-controls.mjs → hooks/animation-controls.mjs} +2 -2
- package/dist/es/animation/{use-animated-state.mjs → hooks/use-animated-state.mjs} +6 -6
- package/dist/es/animation/{use-animation.mjs → hooks/use-animation.mjs} +1 -1
- package/dist/es/animation/index.mjs +121 -0
- package/dist/es/animation/legacy-popmotion/decay.mjs +11 -4
- package/dist/es/animation/legacy-popmotion/index.mjs +23 -15
- package/dist/es/animation/legacy-popmotion/inertia.mjs +14 -8
- package/dist/es/animation/legacy-popmotion/keyframes.mjs +21 -13
- package/dist/es/animation/legacy-popmotion/spring.mjs +33 -39
- package/dist/es/animation/optimized-appear/data-id.mjs +6 -0
- package/dist/es/animation/optimized-appear/handoff.mjs +34 -0
- package/dist/es/animation/optimized-appear/start.mjs +15 -0
- package/dist/es/animation/optimized-appear/store-id.mjs +3 -0
- package/dist/es/animation/utils/default-transitions.mjs +9 -14
- package/dist/es/animation/utils/keyframes.mjs +41 -0
- package/dist/es/animation/utils/transitions.mjs +1 -171
- package/dist/es/animation/waapi/easing.mjs +3 -0
- package/dist/es/animation/waapi/index.mjs +16 -0
- package/dist/es/animation/waapi/supports.mjs +17 -0
- package/dist/es/gestures/drag/VisualElementDragControls.mjs +2 -2
- package/dist/es/index.mjs +6 -3
- package/dist/es/render/utils/animation.mjs +15 -3
- package/dist/es/render/utils/motion-values.mjs +1 -1
- package/dist/es/utils/delay.mjs +3 -0
- package/dist/es/value/index.mjs +2 -2
- package/dist/es/value/use-spring.mjs +1 -2
- package/dist/framer-motion.dev.js +1971 -1865
- package/dist/framer-motion.js +1 -1
- package/dist/index.d.ts +424 -341
- package/dist/projection.dev.js +1655 -1623
- package/dist/size-rollup-dom-animation-assets.js +1 -1
- package/dist/size-rollup-dom-animation.js +1 -1
- package/dist/size-rollup-dom-max-assets.js +1 -1
- package/dist/size-rollup-dom-max.js +1 -1
- package/dist/size-rollup-motion.js +1 -1
- package/dist/size-webpack-dom-animation.js +1 -1
- package/dist/size-webpack-dom-max.js +1 -1
- package/dist/three-entry.d.ts +289 -282
- package/package.json +11 -9
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { createMotionValueAnimation } from './index.mjs';
|
|
1
2
|
import { motionValue } from '../value/index.mjs';
|
|
2
3
|
import { isMotionValue } from '../value/utils/is-motion-value.mjs';
|
|
3
|
-
import { startAnimation } from './utils/transitions.mjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Animate a single value or a `MotionValue`.
|
|
@@ -31,7 +31,7 @@ import { startAnimation } from './utils/transitions.mjs';
|
|
|
31
31
|
*/
|
|
32
32
|
function animate(from, to, transition = {}) {
|
|
33
33
|
const value = isMotionValue(from) ? from : motionValue(from);
|
|
34
|
-
|
|
34
|
+
value.start(createMotionValueAnimation("", value, to, transition));
|
|
35
35
|
return {
|
|
36
36
|
stop: () => value.stop(),
|
|
37
37
|
isAnimating: () => value.isAnimating(),
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { delay } from '../utils/delay.mjs';
|
|
2
|
+
|
|
3
|
+
function createInstantAnimation({ keyframes, elapsed, onUpdate, onComplete, }) {
|
|
4
|
+
const setValue = () => {
|
|
5
|
+
onUpdate && onUpdate(keyframes[keyframes.length - 1]);
|
|
6
|
+
onComplete && onComplete();
|
|
7
|
+
return () => { };
|
|
8
|
+
};
|
|
9
|
+
return elapsed ? delay(setValue, -elapsed) : setValue();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export { createInstantAnimation };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { invariant } from 'hey-listen';
|
|
2
|
-
import { stopAnimation, animateVisualElement } from '
|
|
3
|
-
import { setValues } from '
|
|
2
|
+
import { stopAnimation, animateVisualElement } from '../../render/utils/animation.mjs';
|
|
3
|
+
import { setValues } from '../../render/utils/setters.mjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @public
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { useState, useEffect } from 'react';
|
|
2
|
-
import { useConstant } from '
|
|
3
|
-
import { getOrigin, checkTargetForNewValues } from '
|
|
4
|
-
import { animateVisualElement } from '
|
|
5
|
-
import { makeUseVisualState } from '
|
|
6
|
-
import { createBox } from '
|
|
7
|
-
import { VisualElement } from '
|
|
2
|
+
import { useConstant } from '../../utils/use-constant.mjs';
|
|
3
|
+
import { getOrigin, checkTargetForNewValues } from '../../render/utils/setters.mjs';
|
|
4
|
+
import { animateVisualElement } from '../../render/utils/animation.mjs';
|
|
5
|
+
import { makeUseVisualState } from '../../motion/utils/use-visual-state.mjs';
|
|
6
|
+
import { createBox } from '../../projection/geometry/models.mjs';
|
|
7
|
+
import { VisualElement } from '../../render/VisualElement.mjs';
|
|
8
8
|
|
|
9
9
|
const createObject = () => ({});
|
|
10
10
|
class StateVisualElement extends VisualElement {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { animationControls } from './animation-controls.mjs';
|
|
2
2
|
import { useEffect } from 'react';
|
|
3
|
-
import { useConstant } from '
|
|
3
|
+
import { useConstant } from '../../utils/use-constant.mjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Creates `AnimationControls`, which can be used to manually start, stop
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { warning } from 'hey-listen';
|
|
2
|
+
import { secondsToMilliseconds } from '../utils/time-conversion.mjs';
|
|
3
|
+
import { instantAnimationState } from '../utils/use-instant-transition-state.mjs';
|
|
4
|
+
import { createAcceleratedAnimation } from './create-accelerated-animation.mjs';
|
|
5
|
+
import { createInstantAnimation } from './create-instant-animation.mjs';
|
|
6
|
+
import { animate } from './legacy-popmotion/index.mjs';
|
|
7
|
+
import { inertia } from './legacy-popmotion/inertia.mjs';
|
|
8
|
+
import { getDefaultTransition } from './utils/default-transitions.mjs';
|
|
9
|
+
import { isAnimatable } from './utils/is-animatable.mjs';
|
|
10
|
+
import { getKeyframes } from './utils/keyframes.mjs';
|
|
11
|
+
import { getValueTransition, isTransitionDefined } from './utils/transitions.mjs';
|
|
12
|
+
import { supports } from './waapi/supports.mjs';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A list of values that can be hardware-accelerated.
|
|
16
|
+
*/
|
|
17
|
+
const acceleratedValues = new Set([]);
|
|
18
|
+
const createMotionValueAnimation = (valueName, value, target, transition = {}) => {
|
|
19
|
+
return (onComplete) => {
|
|
20
|
+
const valueTransition = getValueTransition(transition, valueName) || {};
|
|
21
|
+
/**
|
|
22
|
+
* Most transition values are currently completely overwritten by value-specific
|
|
23
|
+
* transitions. In the future it'd be nicer to blend these transitions. But for now
|
|
24
|
+
* delay actually does inherit from the root transition if not value-specific.
|
|
25
|
+
*/
|
|
26
|
+
const delay = valueTransition.delay || transition.delay || 0;
|
|
27
|
+
/**
|
|
28
|
+
* Elapsed isn't a public transition option but can be passed through from
|
|
29
|
+
* optimized appear effects in milliseconds.
|
|
30
|
+
*/
|
|
31
|
+
let { elapsed = 0 } = transition;
|
|
32
|
+
elapsed = elapsed - secondsToMilliseconds(delay);
|
|
33
|
+
const keyframes = getKeyframes(value, valueName, target, valueTransition);
|
|
34
|
+
/**
|
|
35
|
+
* Check if we're able to animate between the start and end keyframes,
|
|
36
|
+
* and throw a warning if we're attempting to animate between one that's
|
|
37
|
+
* animatable and another that isn't.
|
|
38
|
+
*/
|
|
39
|
+
const originKeyframe = keyframes[0];
|
|
40
|
+
const targetKeyframe = keyframes[keyframes.length - 1];
|
|
41
|
+
const isOriginAnimatable = isAnimatable(valueName, originKeyframe);
|
|
42
|
+
const isTargetAnimatable = isAnimatable(valueName, targetKeyframe);
|
|
43
|
+
warning(isOriginAnimatable === isTargetAnimatable, `You are trying to animate ${valueName} from "${originKeyframe}" to "${targetKeyframe}". ${originKeyframe} is not an animatable value - to enable this animation set ${originKeyframe} to a value animatable to ${targetKeyframe} via the \`style\` property.`);
|
|
44
|
+
let options = {
|
|
45
|
+
keyframes,
|
|
46
|
+
velocity: value.getVelocity(),
|
|
47
|
+
...valueTransition,
|
|
48
|
+
elapsed,
|
|
49
|
+
onUpdate: (v) => {
|
|
50
|
+
value.set(v);
|
|
51
|
+
valueTransition.onUpdate && valueTransition.onUpdate(v);
|
|
52
|
+
},
|
|
53
|
+
onComplete: () => {
|
|
54
|
+
onComplete();
|
|
55
|
+
valueTransition.onComplete && valueTransition.onComplete();
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
if (!isOriginAnimatable ||
|
|
59
|
+
!isTargetAnimatable ||
|
|
60
|
+
instantAnimationState.current ||
|
|
61
|
+
valueTransition.type === false) {
|
|
62
|
+
/**
|
|
63
|
+
* If we can't animate this value, or the global instant animation flag is set,
|
|
64
|
+
* or this is simply defined as an instant transition, return an instant transition.
|
|
65
|
+
*/
|
|
66
|
+
return createInstantAnimation(options);
|
|
67
|
+
}
|
|
68
|
+
else if (valueTransition.type === "inertia") {
|
|
69
|
+
/**
|
|
70
|
+
* If this is an inertia animation, we currently don't support pre-generating
|
|
71
|
+
* keyframes for this as such it must always run on the main thread.
|
|
72
|
+
*/
|
|
73
|
+
const animation = inertia(options);
|
|
74
|
+
return () => animation.stop();
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* If there's no transition defined for this value, we can generate
|
|
78
|
+
* unqiue transition settings for this value.
|
|
79
|
+
*/
|
|
80
|
+
if (!isTransitionDefined(valueTransition)) {
|
|
81
|
+
options = {
|
|
82
|
+
...options,
|
|
83
|
+
...getDefaultTransition(valueName, options),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Both WAAPI and our internal animation functions use durations
|
|
88
|
+
* as defined by milliseconds, while our external API defines them
|
|
89
|
+
* as seconds.
|
|
90
|
+
*/
|
|
91
|
+
if (options.duration) {
|
|
92
|
+
options.duration = secondsToMilliseconds(options.duration);
|
|
93
|
+
}
|
|
94
|
+
if (options.repeatDelay) {
|
|
95
|
+
options.repeatDelay = secondsToMilliseconds(options.repeatDelay);
|
|
96
|
+
}
|
|
97
|
+
const canAccelerateAnimation = acceleratedValues.has(valueName) &&
|
|
98
|
+
supports.waapi() &&
|
|
99
|
+
value.owner &&
|
|
100
|
+
!value.owner.getProps().onUpdate &&
|
|
101
|
+
!options.repeat;
|
|
102
|
+
if (canAccelerateAnimation) {
|
|
103
|
+
/**
|
|
104
|
+
* If this animation is capable of being run via WAAPI, then do so.
|
|
105
|
+
*
|
|
106
|
+
* TODO: Currently no values are hardware accelerated so this clause
|
|
107
|
+
* will never trigger. Animation to be added in subsequent PR.
|
|
108
|
+
*/
|
|
109
|
+
return createAcceleratedAnimation();
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
/**
|
|
113
|
+
* Otherwise, fall back to the main thread.
|
|
114
|
+
*/
|
|
115
|
+
const animation = animate(options);
|
|
116
|
+
return () => animation.stop();
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export { createMotionValueAnimation };
|
|
@@ -1,18 +1,25 @@
|
|
|
1
|
-
function decay({
|
|
1
|
+
function decay({
|
|
2
|
+
/**
|
|
3
|
+
* The decay animation dynamically calculates an end of the animation
|
|
4
|
+
* based on the initial keyframe, so we only need to define a single keyframe
|
|
5
|
+
* as default.
|
|
6
|
+
*/
|
|
7
|
+
keyframes = [0], velocity = 0, power = 0.8, timeConstant = 350, restDelta = 0.5, modifyTarget, }) {
|
|
8
|
+
const origin = keyframes[0];
|
|
2
9
|
/**
|
|
3
10
|
* This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
|
|
4
11
|
* to reduce GC during animation.
|
|
5
12
|
*/
|
|
6
|
-
const state = { done: false, value:
|
|
13
|
+
const state = { done: false, value: origin };
|
|
7
14
|
let amplitude = power * velocity;
|
|
8
|
-
const ideal =
|
|
15
|
+
const ideal = origin + amplitude;
|
|
9
16
|
const target = modifyTarget === undefined ? ideal : modifyTarget(ideal);
|
|
10
17
|
/**
|
|
11
18
|
* If the target has changed we need to re-calculate the amplitude, otherwise
|
|
12
19
|
* the animation will start from the wrong position.
|
|
13
20
|
*/
|
|
14
21
|
if (target !== ideal)
|
|
15
|
-
amplitude = target -
|
|
22
|
+
amplitude = target - origin;
|
|
16
23
|
return {
|
|
17
24
|
next: (t) => {
|
|
18
25
|
const delta = -amplitude * Math.exp(-t / timeConstant);
|
|
@@ -4,7 +4,12 @@ import { decay } from './decay.mjs';
|
|
|
4
4
|
import { sync, cancelSync } from '../../frameloop/index.mjs';
|
|
5
5
|
import { interpolate } from '../../utils/interpolate.mjs';
|
|
6
6
|
|
|
7
|
-
const types = {
|
|
7
|
+
const types = {
|
|
8
|
+
decay,
|
|
9
|
+
keyframes: keyframes,
|
|
10
|
+
tween: keyframes,
|
|
11
|
+
spring,
|
|
12
|
+
};
|
|
8
13
|
function loopElapsed(elapsed, duration, delay = 0) {
|
|
9
14
|
return elapsed - duration - delay;
|
|
10
15
|
}
|
|
@@ -23,26 +28,29 @@ const framesync = (update) => {
|
|
|
23
28
|
stop: () => cancelSync.update(passTimestamp),
|
|
24
29
|
};
|
|
25
30
|
};
|
|
26
|
-
function animate({
|
|
31
|
+
function animate({ duration, driver = framesync, elapsed = 0, repeat: repeatMax = 0, repeatType = "loop", repeatDelay = 0, keyframes, onPlay, onStop, onComplete, onRepeat, onUpdate, type = "keyframes", ...options }) {
|
|
27
32
|
var _a, _b;
|
|
28
|
-
let { to } = options;
|
|
29
33
|
let driverControls;
|
|
30
34
|
let repeatCount = 0;
|
|
31
|
-
let computedDuration =
|
|
32
|
-
.duration;
|
|
35
|
+
let computedDuration = duration;
|
|
33
36
|
let latest;
|
|
34
37
|
let isComplete = false;
|
|
35
38
|
let isForwardPlayback = true;
|
|
36
39
|
let interpolateFromNumber;
|
|
37
|
-
const animator = types[
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
const animator = types[keyframes.length > 2 ? "keyframes" : type];
|
|
41
|
+
const origin = keyframes[0];
|
|
42
|
+
const target = keyframes[keyframes.length - 1];
|
|
43
|
+
if ((_b = (_a = animator).needsInterpolation) === null || _b === void 0 ? void 0 : _b.call(_a, origin, target)) {
|
|
44
|
+
interpolateFromNumber = interpolate([0, 100], [origin, target], {
|
|
40
45
|
clamp: false,
|
|
41
46
|
});
|
|
42
|
-
|
|
43
|
-
to = 100;
|
|
47
|
+
keyframes = [0, 100];
|
|
44
48
|
}
|
|
45
|
-
const animation = animator({
|
|
49
|
+
const animation = animator({
|
|
50
|
+
...options,
|
|
51
|
+
duration,
|
|
52
|
+
keyframes,
|
|
53
|
+
});
|
|
46
54
|
function repeat() {
|
|
47
55
|
repeatCount++;
|
|
48
56
|
if (repeatType === "reverse") {
|
|
@@ -72,7 +80,7 @@ function animate({ from, autoplay = true, driver = framesync, elapsed = 0, repea
|
|
|
72
80
|
latest = interpolateFromNumber(latest);
|
|
73
81
|
isComplete = isForwardPlayback ? state.done : elapsed <= 0;
|
|
74
82
|
}
|
|
75
|
-
onUpdate
|
|
83
|
+
onUpdate && onUpdate(latest);
|
|
76
84
|
if (isComplete) {
|
|
77
85
|
if (repeatCount === 0) {
|
|
78
86
|
computedDuration =
|
|
@@ -87,14 +95,14 @@ function animate({ from, autoplay = true, driver = framesync, elapsed = 0, repea
|
|
|
87
95
|
}
|
|
88
96
|
}
|
|
89
97
|
function play() {
|
|
90
|
-
onPlay
|
|
98
|
+
onPlay && onPlay();
|
|
91
99
|
driverControls = driver(update);
|
|
92
100
|
driverControls.start();
|
|
93
101
|
}
|
|
94
|
-
|
|
102
|
+
play();
|
|
95
103
|
return {
|
|
96
104
|
stop: () => {
|
|
97
|
-
onStop
|
|
105
|
+
onStop && onStop();
|
|
98
106
|
driverControls.stop();
|
|
99
107
|
},
|
|
100
108
|
};
|
|
@@ -2,12 +2,13 @@ import { animate } from './index.mjs';
|
|
|
2
2
|
import { velocityPerSecond } from '../../utils/velocity-per-second.mjs';
|
|
3
3
|
import { frameData } from '../../frameloop/data.mjs';
|
|
4
4
|
|
|
5
|
-
function inertia({
|
|
5
|
+
function inertia({ keyframes, velocity = 0, min, max, power = 0.8, timeConstant = 750, bounceStiffness = 500, bounceDamping = 10, restDelta = 1, modifyTarget, driver, onUpdate, onComplete, onStop, }) {
|
|
6
|
+
const origin = keyframes[0];
|
|
6
7
|
let currentAnimation;
|
|
7
8
|
function isOutOfBounds(v) {
|
|
8
9
|
return (min !== undefined && v < min) || (max !== undefined && v > max);
|
|
9
10
|
}
|
|
10
|
-
function
|
|
11
|
+
function findNearestBoundary(v) {
|
|
11
12
|
if (min === undefined)
|
|
12
13
|
return max;
|
|
13
14
|
if (max === undefined)
|
|
@@ -17,6 +18,8 @@ function inertia({ from = 0, velocity = 0, min, max, power = 0.8, timeConstant =
|
|
|
17
18
|
function startAnimation(options) {
|
|
18
19
|
currentAnimation === null || currentAnimation === void 0 ? void 0 : currentAnimation.stop();
|
|
19
20
|
currentAnimation = animate({
|
|
21
|
+
keyframes: [0, 1],
|
|
22
|
+
velocity: 0,
|
|
20
23
|
...options,
|
|
21
24
|
driver,
|
|
22
25
|
onUpdate: (v) => {
|
|
@@ -37,9 +40,12 @@ function inertia({ from = 0, velocity = 0, min, max, power = 0.8, timeConstant =
|
|
|
37
40
|
...options,
|
|
38
41
|
});
|
|
39
42
|
}
|
|
40
|
-
if (isOutOfBounds(
|
|
43
|
+
if (isOutOfBounds(origin)) {
|
|
41
44
|
// Start the animation with spring if outside the defined boundaries
|
|
42
|
-
startSpring({
|
|
45
|
+
startSpring({
|
|
46
|
+
velocity,
|
|
47
|
+
keyframes: [origin, findNearestBoundary(origin)],
|
|
48
|
+
});
|
|
43
49
|
}
|
|
44
50
|
else {
|
|
45
51
|
/**
|
|
@@ -50,10 +56,10 @@ function inertia({ from = 0, velocity = 0, min, max, power = 0.8, timeConstant =
|
|
|
50
56
|
* If it is, we want to check per frame when to switch to a spring
|
|
51
57
|
* animation
|
|
52
58
|
*/
|
|
53
|
-
let target = power * velocity +
|
|
59
|
+
let target = power * velocity + origin;
|
|
54
60
|
if (typeof modifyTarget !== "undefined")
|
|
55
61
|
target = modifyTarget(target);
|
|
56
|
-
const boundary =
|
|
62
|
+
const boundary = findNearestBoundary(target);
|
|
57
63
|
const heading = boundary === min ? -1 : 1;
|
|
58
64
|
let prev;
|
|
59
65
|
let current;
|
|
@@ -63,12 +69,12 @@ function inertia({ from = 0, velocity = 0, min, max, power = 0.8, timeConstant =
|
|
|
63
69
|
velocity = velocityPerSecond(v - prev, frameData.delta);
|
|
64
70
|
if ((heading === 1 && v > boundary) ||
|
|
65
71
|
(heading === -1 && v < boundary)) {
|
|
66
|
-
startSpring({
|
|
72
|
+
startSpring({ keyframes: [v, boundary], velocity });
|
|
67
73
|
}
|
|
68
74
|
};
|
|
69
75
|
startAnimation({
|
|
70
76
|
type: "decay",
|
|
71
|
-
|
|
77
|
+
keyframes: [origin, 0],
|
|
72
78
|
velocity,
|
|
73
79
|
timeConstant,
|
|
74
80
|
power,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { easeInOut } from '../../easing/ease.mjs';
|
|
2
2
|
import { interpolate } from '../../utils/interpolate.mjs';
|
|
3
|
+
import { isEasingArray, easingDefinitionToFunction } from '../utils/easing.mjs';
|
|
3
4
|
|
|
4
5
|
function defaultEasing(values, easing) {
|
|
5
6
|
return values.map(() => easing || easeInOut).splice(0, values.length - 1);
|
|
@@ -11,28 +12,35 @@ function defaultOffset(values) {
|
|
|
11
12
|
function convertOffsetToTimes(offset, duration) {
|
|
12
13
|
return offset.map((o) => o * duration);
|
|
13
14
|
}
|
|
14
|
-
function keyframes({
|
|
15
|
+
function keyframes({ keyframes: keyframeValues, ease = easeInOut, times, duration = 300, }) {
|
|
16
|
+
keyframeValues = [...keyframeValues];
|
|
17
|
+
const origin = keyframes[0];
|
|
15
18
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
19
|
+
* Easing functions can be externally defined as strings. Here we convert them
|
|
20
|
+
* into actual functions.
|
|
18
21
|
*/
|
|
19
|
-
const
|
|
22
|
+
const easingFunctions = isEasingArray(ease)
|
|
23
|
+
? ease.map(easingDefinitionToFunction)
|
|
24
|
+
: easingDefinitionToFunction(ease);
|
|
20
25
|
/**
|
|
21
|
-
*
|
|
26
|
+
* This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
|
|
27
|
+
* to reduce GC during animation.
|
|
22
28
|
*/
|
|
23
|
-
const
|
|
29
|
+
const state = { done: false, value: origin };
|
|
24
30
|
/**
|
|
25
31
|
* Create a times array based on the provided 0-1 offsets
|
|
26
32
|
*/
|
|
27
|
-
const
|
|
33
|
+
const absoluteTimes = convertOffsetToTimes(
|
|
28
34
|
// Only use the provided offsets if they're the correct length
|
|
29
35
|
// TODO Maybe we should warn here if there's a length mismatch
|
|
30
|
-
|
|
31
|
-
?
|
|
32
|
-
: defaultOffset(
|
|
36
|
+
times && times.length === keyframes.length
|
|
37
|
+
? times
|
|
38
|
+
: defaultOffset(keyframeValues), duration);
|
|
33
39
|
function createInterpolator() {
|
|
34
|
-
return interpolate(
|
|
35
|
-
ease: Array.isArray(
|
|
40
|
+
return interpolate(absoluteTimes, keyframeValues, {
|
|
41
|
+
ease: Array.isArray(easingFunctions)
|
|
42
|
+
? easingFunctions
|
|
43
|
+
: defaultEasing(keyframeValues, easingFunctions),
|
|
36
44
|
});
|
|
37
45
|
}
|
|
38
46
|
let interpolator = createInterpolator();
|
|
@@ -43,7 +51,7 @@ function keyframes({ from = 0, to = 1, ease, offset, duration = 300, }) {
|
|
|
43
51
|
return state;
|
|
44
52
|
},
|
|
45
53
|
flipTarget: () => {
|
|
46
|
-
|
|
54
|
+
keyframeValues.reverse();
|
|
47
55
|
interpolator = createInterpolator();
|
|
48
56
|
},
|
|
49
57
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { findSpring, calcAngularFreq } from './find-spring.mjs';
|
|
2
|
+
import { velocityPerSecond } from '../../utils/velocity-per-second.mjs';
|
|
2
3
|
|
|
3
4
|
const durationKeys = ["duration", "bounce"];
|
|
4
5
|
const physicsKeys = ["stiffness", "damping", "mass"];
|
|
@@ -28,36 +29,38 @@ function getSpringOptions(options) {
|
|
|
28
29
|
}
|
|
29
30
|
return springOptions;
|
|
30
31
|
}
|
|
32
|
+
const velocitySampleDuration = 5;
|
|
31
33
|
/**
|
|
32
34
|
* This is based on the spring implementation of Wobble https://github.com/skevy/wobble
|
|
33
35
|
*/
|
|
34
|
-
function spring({
|
|
36
|
+
function spring({ keyframes, restSpeed = 2, restDelta = 0.01, ...options }) {
|
|
37
|
+
let origin = keyframes[0];
|
|
38
|
+
let target = keyframes[keyframes.length - 1];
|
|
35
39
|
/**
|
|
36
40
|
* This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
|
|
37
41
|
* to reduce GC during animation.
|
|
38
42
|
*/
|
|
39
|
-
const state = { done: false, value:
|
|
40
|
-
|
|
43
|
+
const state = { done: false, value: origin };
|
|
44
|
+
const { stiffness, damping, mass, velocity, duration, isResolvedFromDuration, } = getSpringOptions(options);
|
|
41
45
|
let resolveSpring = zero;
|
|
42
|
-
let
|
|
46
|
+
let initialVelocity = velocity ? -(velocity / 1000) : 0.0;
|
|
47
|
+
const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass));
|
|
43
48
|
function createSpring() {
|
|
44
|
-
const
|
|
45
|
-
const initialDelta = to - from;
|
|
46
|
-
const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass));
|
|
49
|
+
const initialDelta = target - origin;
|
|
47
50
|
const undampedAngularFreq = Math.sqrt(stiffness / mass) / 1000;
|
|
48
51
|
/**
|
|
49
52
|
* If we're working within what looks like a 0-1 range, change the default restDelta
|
|
50
53
|
* to 0.01
|
|
51
54
|
*/
|
|
52
55
|
if (restDelta === undefined) {
|
|
53
|
-
restDelta = Math.min(Math.abs(
|
|
56
|
+
restDelta = Math.min(Math.abs(target - origin) / 100, 0.4);
|
|
54
57
|
}
|
|
55
58
|
if (dampingRatio < 1) {
|
|
56
59
|
const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
|
|
57
60
|
// Underdamped spring
|
|
58
61
|
resolveSpring = (t) => {
|
|
59
62
|
const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
|
|
60
|
-
return (
|
|
63
|
+
return (target -
|
|
61
64
|
envelope *
|
|
62
65
|
(((initialVelocity +
|
|
63
66
|
dampingRatio * undampedAngularFreq * initialDelta) /
|
|
@@ -65,33 +68,10 @@ function spring({ from = 0.0, to = 1.0, restSpeed = 2, restDelta = 0.01, ...opti
|
|
|
65
68
|
Math.sin(angularFreq * t) +
|
|
66
69
|
initialDelta * Math.cos(angularFreq * t)));
|
|
67
70
|
};
|
|
68
|
-
resolveVelocity = (t) => {
|
|
69
|
-
// TODO Resolve these calculations with the above
|
|
70
|
-
const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
|
|
71
|
-
return (dampingRatio *
|
|
72
|
-
undampedAngularFreq *
|
|
73
|
-
envelope *
|
|
74
|
-
((Math.sin(angularFreq * t) *
|
|
75
|
-
(initialVelocity +
|
|
76
|
-
dampingRatio *
|
|
77
|
-
undampedAngularFreq *
|
|
78
|
-
initialDelta)) /
|
|
79
|
-
angularFreq +
|
|
80
|
-
initialDelta * Math.cos(angularFreq * t)) -
|
|
81
|
-
envelope *
|
|
82
|
-
(Math.cos(angularFreq * t) *
|
|
83
|
-
(initialVelocity +
|
|
84
|
-
dampingRatio *
|
|
85
|
-
undampedAngularFreq *
|
|
86
|
-
initialDelta) -
|
|
87
|
-
angularFreq *
|
|
88
|
-
initialDelta *
|
|
89
|
-
Math.sin(angularFreq * t)));
|
|
90
|
-
};
|
|
91
71
|
}
|
|
92
72
|
else if (dampingRatio === 1) {
|
|
93
73
|
// Critically damped spring
|
|
94
|
-
resolveSpring = (t) =>
|
|
74
|
+
resolveSpring = (t) => target -
|
|
95
75
|
Math.exp(-undampedAngularFreq * t) *
|
|
96
76
|
(initialDelta +
|
|
97
77
|
(initialVelocity + undampedAngularFreq * initialDelta) *
|
|
@@ -104,7 +84,7 @@ function spring({ from = 0.0, to = 1.0, restSpeed = 2, restDelta = 0.01, ...opti
|
|
|
104
84
|
const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
|
|
105
85
|
// When performing sinh or cosh values can hit Infinity so we cap them here
|
|
106
86
|
const freqForT = Math.min(dampedAngularFreq * t, 300);
|
|
107
|
-
return (
|
|
87
|
+
return (target -
|
|
108
88
|
(envelope *
|
|
109
89
|
((initialVelocity +
|
|
110
90
|
dampingRatio * undampedAngularFreq * initialDelta) *
|
|
@@ -121,21 +101,35 @@ function spring({ from = 0.0, to = 1.0, restSpeed = 2, restDelta = 0.01, ...opti
|
|
|
121
101
|
next: (t) => {
|
|
122
102
|
const current = resolveSpring(t);
|
|
123
103
|
if (!isResolvedFromDuration) {
|
|
124
|
-
|
|
104
|
+
let currentVelocity = initialVelocity;
|
|
105
|
+
if (t !== 0) {
|
|
106
|
+
/**
|
|
107
|
+
* We only need to calculate velocity for under-damped springs
|
|
108
|
+
* as over- and critically-damped springs can't overshoot, so
|
|
109
|
+
* checking only for displacement is enough.
|
|
110
|
+
*/
|
|
111
|
+
if (dampingRatio < 1) {
|
|
112
|
+
const prevT = Math.max(0, t - velocitySampleDuration);
|
|
113
|
+
currentVelocity = velocityPerSecond(current - resolveSpring(prevT), t - prevT);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
currentVelocity = 0;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
125
119
|
const isBelowVelocityThreshold = Math.abs(currentVelocity) <= restSpeed;
|
|
126
|
-
const isBelowDisplacementThreshold = Math.abs(
|
|
120
|
+
const isBelowDisplacementThreshold = Math.abs(target - current) <= restDelta;
|
|
127
121
|
state.done =
|
|
128
122
|
isBelowVelocityThreshold && isBelowDisplacementThreshold;
|
|
129
123
|
}
|
|
130
124
|
else {
|
|
131
125
|
state.done = t >= duration;
|
|
132
126
|
}
|
|
133
|
-
state.value = state.done ?
|
|
127
|
+
state.value = state.done ? target : current;
|
|
134
128
|
return state;
|
|
135
129
|
},
|
|
136
130
|
flipTarget: () => {
|
|
137
|
-
|
|
138
|
-
[
|
|
131
|
+
initialVelocity = -initialVelocity;
|
|
132
|
+
[origin, target] = [target, origin];
|
|
139
133
|
createSpring();
|
|
140
134
|
},
|
|
141
135
|
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { camelToDash } from '../../render/dom/utils/camel-to-dash.mjs';
|
|
2
|
+
|
|
3
|
+
const optimizedAppearDataId = "framerAppearId";
|
|
4
|
+
const optimizedAppearDataAttribute = "data-" + camelToDash(optimizedAppearDataId);
|
|
5
|
+
|
|
6
|
+
export { optimizedAppearDataAttribute, optimizedAppearDataId };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { sync } from '../../frameloop/index.mjs';
|
|
2
|
+
import { transformProps } from '../../render/html/utils/transform.mjs';
|
|
3
|
+
import { appearStoreId } from './store-id.mjs';
|
|
4
|
+
|
|
5
|
+
function handoffOptimizedAppearAnimation(id, name) {
|
|
6
|
+
const { MotionAppearAnimations } = window;
|
|
7
|
+
const animationId = appearStoreId(id, transformProps.has(name) ? "transform" : name);
|
|
8
|
+
const animation = MotionAppearAnimations && MotionAppearAnimations.get(animationId);
|
|
9
|
+
if (animation) {
|
|
10
|
+
/**
|
|
11
|
+
* We allow the animation to persist until the next frame:
|
|
12
|
+
* 1. So it continues to play until Framer Motion is ready to render
|
|
13
|
+
* (avoiding a potential flash of the element's original state)
|
|
14
|
+
* 2. As all independent transforms share a single transform animation, stopping
|
|
15
|
+
* it synchronously would prevent subsequent transforms from handing off.
|
|
16
|
+
*/
|
|
17
|
+
sync.render(() => {
|
|
18
|
+
/**
|
|
19
|
+
* Animation.cancel() throws so it needs to be wrapped in a try/catch
|
|
20
|
+
*/
|
|
21
|
+
try {
|
|
22
|
+
animation.cancel();
|
|
23
|
+
MotionAppearAnimations.delete(animationId);
|
|
24
|
+
}
|
|
25
|
+
catch (e) { }
|
|
26
|
+
});
|
|
27
|
+
return animation.currentTime || 0;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export { handoffOptimizedAppearAnimation };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { appearStoreId } from './store-id.mjs';
|
|
2
|
+
import { animateStyle } from '../waapi/index.mjs';
|
|
3
|
+
import { optimizedAppearDataId } from './data-id.mjs';
|
|
4
|
+
|
|
5
|
+
function startOptimizedAppearAnimation(element, name, keyframes, options) {
|
|
6
|
+
window.MotionAppearAnimations || (window.MotionAppearAnimations = new Map());
|
|
7
|
+
const id = element.dataset[optimizedAppearDataId];
|
|
8
|
+
const animation = animateStyle(element, name, keyframes, options);
|
|
9
|
+
if (id && animation) {
|
|
10
|
+
window.MotionAppearAnimations.set(appearStoreId(id, name), animation);
|
|
11
|
+
}
|
|
12
|
+
return animation;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export { startOptimizedAppearAnimation };
|