framer-motion 7.8.0 → 7.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -5
- package/dist/cjs/index.js +2030 -1920
- package/dist/es/animation/animate.mjs +2 -2
- package/dist/es/animation/create-instant-animation.mjs +12 -0
- package/dist/es/animation/{animation-controls.mjs → hooks/animation-controls.mjs} +2 -2
- package/dist/es/animation/{use-animated-state.mjs → hooks/use-animated-state.mjs} +6 -6
- package/dist/es/animation/{use-animation.mjs → hooks/use-animation.mjs} +1 -1
- package/dist/es/animation/index.mjs +124 -0
- package/dist/es/animation/legacy-popmotion/decay.mjs +11 -4
- package/dist/es/animation/legacy-popmotion/index.mjs +22 -11
- package/dist/es/animation/legacy-popmotion/inertia.mjs +14 -8
- package/dist/es/animation/legacy-popmotion/keyframes.mjs +21 -13
- package/dist/es/animation/legacy-popmotion/spring.mjs +13 -11
- package/dist/es/animation/utils/default-transitions.mjs +9 -14
- package/dist/es/animation/utils/keyframes.mjs +41 -0
- package/dist/es/animation/utils/transitions.mjs +1 -166
- package/dist/es/animation/waapi/create-accelerated-animation.mjs +82 -0
- package/dist/es/animation/waapi/index.mjs +4 -6
- package/dist/es/gestures/drag/VisualElementDragControls.mjs +2 -2
- package/dist/es/index.mjs +3 -3
- package/dist/es/render/VisualElement.mjs +1 -1
- package/dist/es/render/utils/animation.mjs +2 -2
- package/dist/es/render/utils/motion-values.mjs +2 -2
- package/dist/es/render/utils/setters.mjs +1 -1
- package/dist/es/value/index.mjs +11 -5
- package/dist/es/value/use-spring.mjs +1 -2
- package/dist/framer-motion.dev.js +2051 -1941
- package/dist/framer-motion.js +1 -1
- package/dist/index.d.ts +409 -348
- package/dist/projection.dev.js +1672 -1535
- package/dist/size-rollup-dom-animation-assets.js +1 -1
- package/dist/size-rollup-dom-animation.js +1 -1
- package/dist/size-rollup-dom-max-assets.js +1 -1
- package/dist/size-rollup-dom-max.js +1 -1
- package/dist/size-rollup-motion.js +1 -1
- package/dist/size-webpack-dom-animation.js +1 -1
- package/dist/size-webpack-dom-max.js +1 -1
- package/dist/three-entry.d.ts +287 -281
- package/package.json +8 -8
|
@@ -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,124 @@
|
|
|
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 './waapi/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(["opacity"]);
|
|
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 visualElement = value.owner;
|
|
98
|
+
const element = visualElement && visualElement.current;
|
|
99
|
+
const canAccelerateAnimation = supports.waapi() &&
|
|
100
|
+
acceleratedValues.has(valueName) &&
|
|
101
|
+
!options.repeatDelay &&
|
|
102
|
+
options.repeatType !== "mirror" &&
|
|
103
|
+
options.damping !== 0 &&
|
|
104
|
+
typeof options.ease !== "function" &&
|
|
105
|
+
visualElement &&
|
|
106
|
+
element instanceof HTMLElement &&
|
|
107
|
+
!visualElement.getProps().onUpdate;
|
|
108
|
+
if (canAccelerateAnimation) {
|
|
109
|
+
/**
|
|
110
|
+
* If this animation is capable of being run via WAAPI, then do so.
|
|
111
|
+
*/
|
|
112
|
+
return createAcceleratedAnimation(value, valueName, options);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
/**
|
|
116
|
+
* Otherwise, fall back to the main thread.
|
|
117
|
+
*/
|
|
118
|
+
const animation = animate(options);
|
|
119
|
+
return () => animation.stop();
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
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, autoplay = true, 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") {
|
|
@@ -97,6 +105,9 @@ function animate({ from, autoplay = true, driver = framesync, elapsed = 0, repea
|
|
|
97
105
|
onStop && onStop();
|
|
98
106
|
driverControls.stop();
|
|
99
107
|
},
|
|
108
|
+
sample: (t) => {
|
|
109
|
+
return animation.next(Math.max(0, t)).value;
|
|
110
|
+
},
|
|
100
111
|
};
|
|
101
112
|
}
|
|
102
113
|
|
|
@@ -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
|
};
|
|
@@ -33,32 +33,34 @@ 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({
|
|
36
|
+
function spring({ keyframes, restSpeed = 2, restDelta = 0.01, ...options }) {
|
|
37
|
+
let origin = keyframes[0];
|
|
38
|
+
let target = keyframes[keyframes.length - 1];
|
|
37
39
|
/**
|
|
38
40
|
* This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
|
|
39
41
|
* to reduce GC during animation.
|
|
40
42
|
*/
|
|
41
|
-
const state = { done: false, value:
|
|
42
|
-
|
|
43
|
+
const state = { done: false, value: origin };
|
|
44
|
+
const { stiffness, damping, mass, velocity, duration, isResolvedFromDuration, } = getSpringOptions(options);
|
|
43
45
|
let resolveSpring = zero;
|
|
44
46
|
let initialVelocity = velocity ? -(velocity / 1000) : 0.0;
|
|
45
47
|
const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass));
|
|
46
48
|
function createSpring() {
|
|
47
|
-
const initialDelta =
|
|
49
|
+
const initialDelta = target - origin;
|
|
48
50
|
const undampedAngularFreq = Math.sqrt(stiffness / mass) / 1000;
|
|
49
51
|
/**
|
|
50
52
|
* If we're working within what looks like a 0-1 range, change the default restDelta
|
|
51
53
|
* to 0.01
|
|
52
54
|
*/
|
|
53
55
|
if (restDelta === undefined) {
|
|
54
|
-
restDelta = Math.min(Math.abs(
|
|
56
|
+
restDelta = Math.min(Math.abs(target - origin) / 100, 0.4);
|
|
55
57
|
}
|
|
56
58
|
if (dampingRatio < 1) {
|
|
57
59
|
const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
|
|
58
60
|
// Underdamped spring
|
|
59
61
|
resolveSpring = (t) => {
|
|
60
62
|
const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
|
|
61
|
-
return (
|
|
63
|
+
return (target -
|
|
62
64
|
envelope *
|
|
63
65
|
(((initialVelocity +
|
|
64
66
|
dampingRatio * undampedAngularFreq * initialDelta) /
|
|
@@ -69,7 +71,7 @@ function spring({ from = 0.0, to = 1.0, restSpeed = 2, restDelta = 0.01, ...opti
|
|
|
69
71
|
}
|
|
70
72
|
else if (dampingRatio === 1) {
|
|
71
73
|
// Critically damped spring
|
|
72
|
-
resolveSpring = (t) =>
|
|
74
|
+
resolveSpring = (t) => target -
|
|
73
75
|
Math.exp(-undampedAngularFreq * t) *
|
|
74
76
|
(initialDelta +
|
|
75
77
|
(initialVelocity + undampedAngularFreq * initialDelta) *
|
|
@@ -82,7 +84,7 @@ function spring({ from = 0.0, to = 1.0, restSpeed = 2, restDelta = 0.01, ...opti
|
|
|
82
84
|
const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
|
|
83
85
|
// When performing sinh or cosh values can hit Infinity so we cap them here
|
|
84
86
|
const freqForT = Math.min(dampedAngularFreq * t, 300);
|
|
85
|
-
return (
|
|
87
|
+
return (target -
|
|
86
88
|
(envelope *
|
|
87
89
|
((initialVelocity +
|
|
88
90
|
dampingRatio * undampedAngularFreq * initialDelta) *
|
|
@@ -115,19 +117,19 @@ function spring({ from = 0.0, to = 1.0, restSpeed = 2, restDelta = 0.01, ...opti
|
|
|
115
117
|
}
|
|
116
118
|
}
|
|
117
119
|
const isBelowVelocityThreshold = Math.abs(currentVelocity) <= restSpeed;
|
|
118
|
-
const isBelowDisplacementThreshold = Math.abs(
|
|
120
|
+
const isBelowDisplacementThreshold = Math.abs(target - current) <= restDelta;
|
|
119
121
|
state.done =
|
|
120
122
|
isBelowVelocityThreshold && isBelowDisplacementThreshold;
|
|
121
123
|
}
|
|
122
124
|
else {
|
|
123
125
|
state.done = t >= duration;
|
|
124
126
|
}
|
|
125
|
-
state.value = state.done ?
|
|
127
|
+
state.value = state.done ? target : current;
|
|
126
128
|
return state;
|
|
127
129
|
},
|
|
128
130
|
flipTarget: () => {
|
|
129
131
|
initialVelocity = -initialVelocity;
|
|
130
|
-
[
|
|
132
|
+
[origin, target] = [target, origin];
|
|
131
133
|
createSpring();
|
|
132
134
|
},
|
|
133
135
|
};
|
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
import { isKeyframesTarget } from './is-keyframes-target.mjs';
|
|
2
|
-
|
|
3
1
|
const underDampedSpring = () => ({
|
|
4
2
|
type: "spring",
|
|
5
3
|
stiffness: 500,
|
|
6
4
|
damping: 25,
|
|
7
5
|
restSpeed: 10,
|
|
8
6
|
});
|
|
9
|
-
const criticallyDampedSpring = (
|
|
7
|
+
const criticallyDampedSpring = (target) => ({
|
|
10
8
|
type: "spring",
|
|
11
9
|
stiffness: 550,
|
|
12
|
-
damping:
|
|
10
|
+
damping: target === 0 ? 2 * Math.sqrt(550) : 30,
|
|
13
11
|
restSpeed: 10,
|
|
14
12
|
});
|
|
15
13
|
const linearTween = () => ({
|
|
@@ -17,11 +15,10 @@ const linearTween = () => ({
|
|
|
17
15
|
ease: "linear",
|
|
18
16
|
duration: 0.3,
|
|
19
17
|
});
|
|
20
|
-
const
|
|
18
|
+
const keyframesTransition = {
|
|
21
19
|
type: "keyframes",
|
|
22
20
|
duration: 0.8,
|
|
23
|
-
|
|
24
|
-
});
|
|
21
|
+
};
|
|
25
22
|
const defaultTransitions = {
|
|
26
23
|
x: underDampedSpring,
|
|
27
24
|
y: underDampedSpring,
|
|
@@ -38,16 +35,14 @@ const defaultTransitions = {
|
|
|
38
35
|
color: linearTween,
|
|
39
36
|
default: criticallyDampedSpring,
|
|
40
37
|
};
|
|
41
|
-
const getDefaultTransition = (valueKey,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
transitionFactory = keyframes;
|
|
38
|
+
const getDefaultTransition = (valueKey, { keyframes }) => {
|
|
39
|
+
if (keyframes.length > 2) {
|
|
40
|
+
return keyframesTransition;
|
|
45
41
|
}
|
|
46
42
|
else {
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
const factory = defaultTransitions[valueKey] || defaultTransitions.default;
|
|
44
|
+
return factory(keyframes[1]);
|
|
49
45
|
}
|
|
50
|
-
return { to, ...transitionFactory(to) };
|
|
51
46
|
};
|
|
52
47
|
|
|
53
48
|
export { criticallyDampedSpring, getDefaultTransition, linearTween, underDampedSpring };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { getAnimatableNone } from '../../render/dom/value-types/animatable-none.mjs';
|
|
2
|
+
import { isAnimatable } from './is-animatable.mjs';
|
|
3
|
+
import { isZero, getZeroUnit } from './transitions.mjs';
|
|
4
|
+
|
|
5
|
+
function getKeyframes(value, valueName, target, transition) {
|
|
6
|
+
const isTargetAnimatable = isAnimatable(valueName, target);
|
|
7
|
+
let origin = transition.from !== undefined ? transition.from : value.get();
|
|
8
|
+
if (origin === "none" && isTargetAnimatable && typeof target === "string") {
|
|
9
|
+
/**
|
|
10
|
+
* If we're trying to animate from "none", try and get an animatable version
|
|
11
|
+
* of the target. This could be improved to work both ways.
|
|
12
|
+
*/
|
|
13
|
+
origin = getAnimatableNone(valueName, target);
|
|
14
|
+
}
|
|
15
|
+
else if (isZero(origin) && typeof target === "string") {
|
|
16
|
+
origin = getZeroUnit(target);
|
|
17
|
+
}
|
|
18
|
+
else if (!Array.isArray(target) &&
|
|
19
|
+
isZero(target) &&
|
|
20
|
+
typeof origin === "string") {
|
|
21
|
+
target = getZeroUnit(origin);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* If the target has been defined as a series of keyframes
|
|
25
|
+
*/
|
|
26
|
+
if (Array.isArray(target)) {
|
|
27
|
+
/**
|
|
28
|
+
* Ensure an initial wildcard keyframe is hydrated by the origin.
|
|
29
|
+
* TODO: Support extra wildcard keyframes i.e [1, null, 0]
|
|
30
|
+
*/
|
|
31
|
+
if (target[0] === null) {
|
|
32
|
+
target[0] = origin;
|
|
33
|
+
}
|
|
34
|
+
return target;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
return [origin, target];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export { getKeyframes };
|