framer-motion 10.2.2 → 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-b7ab39cb.js → wrap-62da7859.js} +456 -438
- package/dist/dom-entry.d.ts +497 -49
- package/dist/es/animation/GroupPlaybackControls.mjs +25 -0
- package/dist/es/animation/animate.mjs +2 -4
- 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 -459
- package/dist/framer-motion.js +1 -1
- package/dist/index.d.ts +70 -114
- package/dist/projection.dev.js +5849 -5831
- package/dist/three-entry.d.ts +11 -9
- package/package.json +7 -7
- 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
package/dist/three-entry.d.ts
CHANGED
|
@@ -852,6 +852,13 @@ interface CustomValueType {
|
|
|
852
852
|
toValue: () => number | string;
|
|
853
853
|
}
|
|
854
854
|
|
|
855
|
+
/**
|
|
856
|
+
* @public
|
|
857
|
+
*/
|
|
858
|
+
interface AnimationPlaybackControls {
|
|
859
|
+
currentTime: number;
|
|
860
|
+
stop: () => void;
|
|
861
|
+
}
|
|
855
862
|
/**
|
|
856
863
|
* @public
|
|
857
864
|
*/
|
|
@@ -1233,14 +1240,6 @@ declare class NodeStack {
|
|
|
1233
1240
|
removeLeadSnapshot(): void;
|
|
1234
1241
|
}
|
|
1235
1242
|
|
|
1236
|
-
/**
|
|
1237
|
-
* @public
|
|
1238
|
-
*/
|
|
1239
|
-
interface AnimationPlaybackControls {
|
|
1240
|
-
stop: () => void;
|
|
1241
|
-
isAnimating: () => boolean;
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
1243
|
interface WithDepth {
|
|
1245
1244
|
depth: number;
|
|
1246
1245
|
}
|
|
@@ -1414,9 +1413,12 @@ declare function MeasureLayout(props: MotionProps & {
|
|
|
1414
1413
|
visualElement: VisualElement;
|
|
1415
1414
|
}): JSX.Element;
|
|
1416
1415
|
|
|
1416
|
+
interface FeatureClass<Props = unknown> {
|
|
1417
|
+
new (props: Props): Feature<Props>;
|
|
1418
|
+
}
|
|
1417
1419
|
declare type HydratedFeatureDefinition = {
|
|
1418
1420
|
isEnabled: (props: MotionProps) => boolean;
|
|
1419
|
-
Feature:
|
|
1421
|
+
Feature: FeatureClass<unknown>;
|
|
1420
1422
|
ProjectionNode?: any;
|
|
1421
1423
|
MeasureLayout?: typeof MeasureLayout;
|
|
1422
1424
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "framer-motion",
|
|
3
|
-
"version": "10.2.
|
|
3
|
+
"version": "10.2.4",
|
|
4
4
|
"description": "A simple and powerful React and JavaScript animation library",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"module": "dist/es/index.mjs",
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"bundlesize": [
|
|
86
86
|
{
|
|
87
87
|
"path": "./dist/size-rollup-motion.js",
|
|
88
|
-
"maxSize": "29.
|
|
88
|
+
"maxSize": "29.85 kB"
|
|
89
89
|
},
|
|
90
90
|
{
|
|
91
91
|
"path": "./dist/size-rollup-m.js",
|
|
@@ -93,11 +93,11 @@
|
|
|
93
93
|
},
|
|
94
94
|
{
|
|
95
95
|
"path": "./dist/size-rollup-dom-animation.js",
|
|
96
|
-
"maxSize": "14.
|
|
96
|
+
"maxSize": "14.56 kB"
|
|
97
97
|
},
|
|
98
98
|
{
|
|
99
99
|
"path": "./dist/size-rollup-dom-max.js",
|
|
100
|
-
"maxSize": "25.
|
|
100
|
+
"maxSize": "25.6 kB"
|
|
101
101
|
},
|
|
102
102
|
{
|
|
103
103
|
"path": "./dist/size-rollup-animate.js",
|
|
@@ -109,12 +109,12 @@
|
|
|
109
109
|
},
|
|
110
110
|
{
|
|
111
111
|
"path": "./dist/size-webpack-dom-animation.js",
|
|
112
|
-
"maxSize": "18.
|
|
112
|
+
"maxSize": "18.56 kB"
|
|
113
113
|
},
|
|
114
114
|
{
|
|
115
115
|
"path": "./dist/size-webpack-dom-max.js",
|
|
116
|
-
"maxSize": "30.
|
|
116
|
+
"maxSize": "30.42 kB"
|
|
117
117
|
}
|
|
118
118
|
],
|
|
119
|
-
"gitHead": "
|
|
119
|
+
"gitHead": "82eb0ba26cd103d524a27dc95415e437cce35822"
|
|
120
120
|
}
|
|
@@ -1,34 +0,0 @@
|
|
|
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];
|
|
9
|
-
/**
|
|
10
|
-
* This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
|
|
11
|
-
* to reduce GC during animation.
|
|
12
|
-
*/
|
|
13
|
-
const state = { done: false, value: origin };
|
|
14
|
-
let amplitude = power * velocity;
|
|
15
|
-
const ideal = origin + amplitude;
|
|
16
|
-
const target = modifyTarget === undefined ? ideal : modifyTarget(ideal);
|
|
17
|
-
/**
|
|
18
|
-
* If the target has changed we need to re-calculate the amplitude, otherwise
|
|
19
|
-
* the animation will start from the wrong position.
|
|
20
|
-
*/
|
|
21
|
-
if (target !== ideal)
|
|
22
|
-
amplitude = target - origin;
|
|
23
|
-
return {
|
|
24
|
-
next: (t) => {
|
|
25
|
-
const delta = -amplitude * Math.exp(-t / timeConstant);
|
|
26
|
-
state.done = !(delta > restDelta || delta < -restDelta);
|
|
27
|
-
state.value = state.done ? target : target + delta;
|
|
28
|
-
return state;
|
|
29
|
-
},
|
|
30
|
-
flipTarget: () => { },
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export { decay };
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import { keyframes } from './keyframes.mjs';
|
|
2
|
-
import { spring } from './spring.mjs';
|
|
3
|
-
import { decay } from './decay.mjs';
|
|
4
|
-
import { sync, cancelSync } from '../../frameloop/index.mjs';
|
|
5
|
-
import { interpolate } from '../../utils/interpolate.mjs';
|
|
6
|
-
|
|
7
|
-
const types = {
|
|
8
|
-
decay,
|
|
9
|
-
keyframes: keyframes,
|
|
10
|
-
tween: keyframes,
|
|
11
|
-
spring,
|
|
12
|
-
};
|
|
13
|
-
function loopElapsed(elapsed, duration, delay = 0) {
|
|
14
|
-
return elapsed - duration - delay;
|
|
15
|
-
}
|
|
16
|
-
function reverseElapsed(elapsed, duration = 0, delay = 0, isForwardPlayback = true) {
|
|
17
|
-
return isForwardPlayback
|
|
18
|
-
? loopElapsed(duration + -elapsed, duration, delay)
|
|
19
|
-
: duration - (elapsed - duration) + delay;
|
|
20
|
-
}
|
|
21
|
-
function hasRepeatDelayElapsed(elapsed, duration, delay, isForwardPlayback) {
|
|
22
|
-
return isForwardPlayback ? elapsed >= duration + delay : elapsed <= -delay;
|
|
23
|
-
}
|
|
24
|
-
const framesync = (update) => {
|
|
25
|
-
const passTimestamp = ({ delta }) => update(delta);
|
|
26
|
-
return {
|
|
27
|
-
start: () => sync.update(passTimestamp, true),
|
|
28
|
-
stop: () => cancelSync.update(passTimestamp),
|
|
29
|
-
};
|
|
30
|
-
};
|
|
31
|
-
function animateValue({ duration, driver = framesync, elapsed = 0, repeat: repeatMax = 0, repeatType = "loop", repeatDelay = 0, keyframes: keyframes$1, autoplay = true, onPlay, onStop, onComplete, onRepeat, onUpdate, type = "keyframes", ...options }) {
|
|
32
|
-
const initialElapsed = elapsed;
|
|
33
|
-
let driverControls;
|
|
34
|
-
let repeatCount = 0;
|
|
35
|
-
let computedDuration = duration;
|
|
36
|
-
let isComplete = false;
|
|
37
|
-
let isForwardPlayback = true;
|
|
38
|
-
let interpolateFromNumber;
|
|
39
|
-
const animator = types[keyframes$1.length > 2 ? "keyframes" : type] || keyframes;
|
|
40
|
-
const origin = keyframes$1[0];
|
|
41
|
-
const target = keyframes$1[keyframes$1.length - 1];
|
|
42
|
-
let state = { done: false, value: origin };
|
|
43
|
-
/**
|
|
44
|
-
* If this value needs interpolation (ie is non-numerical), set up an interpolator.
|
|
45
|
-
* TODO: Keyframes animation also performs this step. This could be removed so it only happens here.
|
|
46
|
-
*/
|
|
47
|
-
const { needsInterpolation } = animator;
|
|
48
|
-
if (needsInterpolation && needsInterpolation(origin, target)) {
|
|
49
|
-
interpolateFromNumber = interpolate([0, 100], [origin, target], {
|
|
50
|
-
clamp: false,
|
|
51
|
-
});
|
|
52
|
-
keyframes$1 = [0, 100];
|
|
53
|
-
}
|
|
54
|
-
const animation = animator({
|
|
55
|
-
...options,
|
|
56
|
-
duration,
|
|
57
|
-
keyframes: keyframes$1,
|
|
58
|
-
});
|
|
59
|
-
function repeat() {
|
|
60
|
-
repeatCount++;
|
|
61
|
-
if (repeatType === "reverse") {
|
|
62
|
-
isForwardPlayback = repeatCount % 2 === 0;
|
|
63
|
-
elapsed = reverseElapsed(elapsed, computedDuration, repeatDelay, isForwardPlayback);
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
elapsed = loopElapsed(elapsed, computedDuration, repeatDelay);
|
|
67
|
-
if (repeatType === "mirror")
|
|
68
|
-
animation.flipTarget();
|
|
69
|
-
}
|
|
70
|
-
isComplete = false;
|
|
71
|
-
onRepeat && onRepeat();
|
|
72
|
-
}
|
|
73
|
-
function complete() {
|
|
74
|
-
driverControls && driverControls.stop();
|
|
75
|
-
onComplete && onComplete();
|
|
76
|
-
}
|
|
77
|
-
function update(delta) {
|
|
78
|
-
if (!isForwardPlayback)
|
|
79
|
-
delta = -delta;
|
|
80
|
-
elapsed += delta;
|
|
81
|
-
if (!isComplete) {
|
|
82
|
-
state = animation.next(Math.max(0, elapsed));
|
|
83
|
-
if (interpolateFromNumber)
|
|
84
|
-
state.value = interpolateFromNumber(state.value);
|
|
85
|
-
isComplete = isForwardPlayback ? state.done : elapsed <= 0;
|
|
86
|
-
}
|
|
87
|
-
onUpdate && onUpdate(state.value);
|
|
88
|
-
if (isComplete) {
|
|
89
|
-
if (repeatCount === 0) {
|
|
90
|
-
computedDuration =
|
|
91
|
-
computedDuration !== undefined ? computedDuration : elapsed;
|
|
92
|
-
}
|
|
93
|
-
if (repeatCount < repeatMax) {
|
|
94
|
-
hasRepeatDelayElapsed(elapsed, computedDuration, repeatDelay, isForwardPlayback) && repeat();
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
complete();
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
function play() {
|
|
102
|
-
onPlay && onPlay();
|
|
103
|
-
driverControls = driver(update);
|
|
104
|
-
driverControls.start();
|
|
105
|
-
}
|
|
106
|
-
autoplay && play();
|
|
107
|
-
return {
|
|
108
|
-
stop: () => {
|
|
109
|
-
onStop && onStop();
|
|
110
|
-
driverControls && driverControls.stop();
|
|
111
|
-
},
|
|
112
|
-
/**
|
|
113
|
-
* Set the current time of the animation. This is purposefully
|
|
114
|
-
* mirroring the WAAPI animation API to make them interchanagable.
|
|
115
|
-
* Going forward this file should be ported more towards
|
|
116
|
-
* https://github.com/motiondivision/motionone/blob/main/packages/animation/src/Animation.ts
|
|
117
|
-
* Which behaviourally adheres to WAAPI as far as possible.
|
|
118
|
-
*
|
|
119
|
-
* WARNING: This is not safe to use for most animations. We currently
|
|
120
|
-
* only use it for handoff from WAAPI within Framer.
|
|
121
|
-
*
|
|
122
|
-
* This animation function consumes time every frame rather than being sampled for time.
|
|
123
|
-
* So the sample() method performs some headless frames to ensure
|
|
124
|
-
* repeats are handled correctly. Ideally in the future we will replace
|
|
125
|
-
* that method with this, once repeat calculations are pure.
|
|
126
|
-
*/
|
|
127
|
-
set currentTime(t) {
|
|
128
|
-
elapsed = initialElapsed;
|
|
129
|
-
update(t);
|
|
130
|
-
},
|
|
131
|
-
/**
|
|
132
|
-
* animate() can't yet be sampled for time, instead it
|
|
133
|
-
* consumes time. So to sample it we have to run a low
|
|
134
|
-
* temporal-resolution version.
|
|
135
|
-
*
|
|
136
|
-
* isControlled should be set to true if sample is being run within
|
|
137
|
-
* a loop. This indicates that we're not arbitrarily sampling
|
|
138
|
-
* the animation but running it one step after another. Therefore
|
|
139
|
-
* we don't need to run a low-res version here. This is a stop-gap
|
|
140
|
-
* until a rewrite can sample for time.
|
|
141
|
-
*/
|
|
142
|
-
sample: (t, isControlled = false) => {
|
|
143
|
-
elapsed = initialElapsed;
|
|
144
|
-
if (isControlled) {
|
|
145
|
-
update(t);
|
|
146
|
-
return state;
|
|
147
|
-
}
|
|
148
|
-
const sampleResolution = duration && typeof duration === "number"
|
|
149
|
-
? Math.max(duration * 0.5, 50)
|
|
150
|
-
: 50;
|
|
151
|
-
let sampleElapsed = 0;
|
|
152
|
-
update(0);
|
|
153
|
-
while (sampleElapsed <= t) {
|
|
154
|
-
const remaining = t - sampleElapsed;
|
|
155
|
-
update(Math.min(remaining, sampleResolution));
|
|
156
|
-
sampleElapsed += sampleResolution;
|
|
157
|
-
}
|
|
158
|
-
return state;
|
|
159
|
-
},
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export { animateValue, hasRepeatDelayElapsed, loopElapsed, reverseElapsed };
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { animateValue } from './index.mjs';
|
|
2
|
-
import { velocityPerSecond } from '../../utils/velocity-per-second.mjs';
|
|
3
|
-
import { frameData } from '../../frameloop/data.mjs';
|
|
4
|
-
|
|
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];
|
|
7
|
-
let currentAnimation;
|
|
8
|
-
function isOutOfBounds(v) {
|
|
9
|
-
return (min !== undefined && v < min) || (max !== undefined && v > max);
|
|
10
|
-
}
|
|
11
|
-
function findNearestBoundary(v) {
|
|
12
|
-
if (min === undefined)
|
|
13
|
-
return max;
|
|
14
|
-
if (max === undefined)
|
|
15
|
-
return min;
|
|
16
|
-
return Math.abs(min - v) < Math.abs(max - v) ? min : max;
|
|
17
|
-
}
|
|
18
|
-
function startAnimation(options) {
|
|
19
|
-
currentAnimation && currentAnimation.stop();
|
|
20
|
-
currentAnimation = animateValue({
|
|
21
|
-
keyframes: [0, 1],
|
|
22
|
-
velocity: 0,
|
|
23
|
-
...options,
|
|
24
|
-
driver,
|
|
25
|
-
onUpdate: (v) => {
|
|
26
|
-
onUpdate && onUpdate(v);
|
|
27
|
-
options.onUpdate && options.onUpdate(v);
|
|
28
|
-
},
|
|
29
|
-
onComplete,
|
|
30
|
-
onStop,
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
function startSpring(options) {
|
|
34
|
-
startAnimation({
|
|
35
|
-
type: "spring",
|
|
36
|
-
stiffness: bounceStiffness,
|
|
37
|
-
damping: bounceDamping,
|
|
38
|
-
restDelta,
|
|
39
|
-
...options,
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
if (isOutOfBounds(origin)) {
|
|
43
|
-
// Start the animation with spring if outside the defined boundaries
|
|
44
|
-
startSpring({
|
|
45
|
-
velocity,
|
|
46
|
-
keyframes: [origin, findNearestBoundary(origin)],
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
/**
|
|
51
|
-
* Or if the value is out of bounds, simulate the inertia movement
|
|
52
|
-
* with the decay animation.
|
|
53
|
-
*
|
|
54
|
-
* Pre-calculate the target so we can detect if it's out-of-bounds.
|
|
55
|
-
* If it is, we want to check per frame when to switch to a spring
|
|
56
|
-
* animation
|
|
57
|
-
*/
|
|
58
|
-
let target = power * velocity + origin;
|
|
59
|
-
if (typeof modifyTarget !== "undefined")
|
|
60
|
-
target = modifyTarget(target);
|
|
61
|
-
const boundary = findNearestBoundary(target);
|
|
62
|
-
const heading = boundary === min ? -1 : 1;
|
|
63
|
-
let prev;
|
|
64
|
-
let current;
|
|
65
|
-
const checkBoundary = (v) => {
|
|
66
|
-
prev = current;
|
|
67
|
-
current = v;
|
|
68
|
-
velocity = velocityPerSecond(v - prev, frameData.delta);
|
|
69
|
-
if ((heading === 1 && v > boundary) ||
|
|
70
|
-
(heading === -1 && v < boundary)) {
|
|
71
|
-
startSpring({ keyframes: [v, boundary], velocity });
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
startAnimation({
|
|
75
|
-
type: "decay",
|
|
76
|
-
keyframes: [origin, 0],
|
|
77
|
-
velocity,
|
|
78
|
-
timeConstant,
|
|
79
|
-
power,
|
|
80
|
-
restDelta,
|
|
81
|
-
modifyTarget,
|
|
82
|
-
onUpdate: isOutOfBounds(target) ? checkBoundary : undefined,
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
return {
|
|
86
|
-
stop: () => currentAnimation && currentAnimation.stop(),
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export { inertia };
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import { findSpring, calcAngularFreq } from './find-spring.mjs';
|
|
2
|
-
import { velocityPerSecond } from '../../utils/velocity-per-second.mjs';
|
|
3
|
-
|
|
4
|
-
const durationKeys = ["duration", "bounce"];
|
|
5
|
-
const physicsKeys = ["stiffness", "damping", "mass"];
|
|
6
|
-
function isSpringType(options, keys) {
|
|
7
|
-
return keys.some((key) => options[key] !== undefined);
|
|
8
|
-
}
|
|
9
|
-
function getSpringOptions(options) {
|
|
10
|
-
let springOptions = {
|
|
11
|
-
velocity: 0.0,
|
|
12
|
-
stiffness: 100,
|
|
13
|
-
damping: 10,
|
|
14
|
-
mass: 1.0,
|
|
15
|
-
isResolvedFromDuration: false,
|
|
16
|
-
...options,
|
|
17
|
-
};
|
|
18
|
-
// stiffness/damping/mass overrides duration/bounce
|
|
19
|
-
if (!isSpringType(options, physicsKeys) &&
|
|
20
|
-
isSpringType(options, durationKeys)) {
|
|
21
|
-
const derived = findSpring(options);
|
|
22
|
-
springOptions = {
|
|
23
|
-
...springOptions,
|
|
24
|
-
...derived,
|
|
25
|
-
velocity: 0.0,
|
|
26
|
-
mass: 1.0,
|
|
27
|
-
};
|
|
28
|
-
springOptions.isResolvedFromDuration = true;
|
|
29
|
-
}
|
|
30
|
-
return springOptions;
|
|
31
|
-
}
|
|
32
|
-
const velocitySampleDuration = 5;
|
|
33
|
-
/**
|
|
34
|
-
* This is based on the spring implementation of Wobble https://github.com/skevy/wobble
|
|
35
|
-
*/
|
|
36
|
-
function spring({ keyframes, restDelta, restSpeed, ...options }) {
|
|
37
|
-
let origin = keyframes[0];
|
|
38
|
-
let target = keyframes[keyframes.length - 1];
|
|
39
|
-
/**
|
|
40
|
-
* This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
|
|
41
|
-
* to reduce GC during animation.
|
|
42
|
-
*/
|
|
43
|
-
const state = { done: false, value: origin };
|
|
44
|
-
const { stiffness, damping, mass, velocity, duration, isResolvedFromDuration, } = getSpringOptions(options);
|
|
45
|
-
let resolveSpring = zero;
|
|
46
|
-
let initialVelocity = velocity ? -(velocity / 1000) : 0.0;
|
|
47
|
-
const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass));
|
|
48
|
-
function createSpring() {
|
|
49
|
-
const initialDelta = target - origin;
|
|
50
|
-
const undampedAngularFreq = Math.sqrt(stiffness / mass) / 1000;
|
|
51
|
-
/**
|
|
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.
|
|
57
|
-
*/
|
|
58
|
-
const isGranularScale = Math.abs(initialDelta) < 5;
|
|
59
|
-
restSpeed || (restSpeed = isGranularScale ? 0.01 : 2);
|
|
60
|
-
restDelta || (restDelta = isGranularScale ? 0.005 : 0.5);
|
|
61
|
-
if (dampingRatio < 1) {
|
|
62
|
-
const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
|
|
63
|
-
// Underdamped spring
|
|
64
|
-
resolveSpring = (t) => {
|
|
65
|
-
const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
|
|
66
|
-
return (target -
|
|
67
|
-
envelope *
|
|
68
|
-
(((initialVelocity +
|
|
69
|
-
dampingRatio * undampedAngularFreq * initialDelta) /
|
|
70
|
-
angularFreq) *
|
|
71
|
-
Math.sin(angularFreq * t) +
|
|
72
|
-
initialDelta * Math.cos(angularFreq * t)));
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
else if (dampingRatio === 1) {
|
|
76
|
-
// Critically damped spring
|
|
77
|
-
resolveSpring = (t) => target -
|
|
78
|
-
Math.exp(-undampedAngularFreq * t) *
|
|
79
|
-
(initialDelta +
|
|
80
|
-
(initialVelocity + undampedAngularFreq * initialDelta) *
|
|
81
|
-
t);
|
|
82
|
-
}
|
|
83
|
-
else {
|
|
84
|
-
// Overdamped spring
|
|
85
|
-
const dampedAngularFreq = undampedAngularFreq * Math.sqrt(dampingRatio * dampingRatio - 1);
|
|
86
|
-
resolveSpring = (t) => {
|
|
87
|
-
const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
|
|
88
|
-
// When performing sinh or cosh values can hit Infinity so we cap them here
|
|
89
|
-
const freqForT = Math.min(dampedAngularFreq * t, 300);
|
|
90
|
-
return (target -
|
|
91
|
-
(envelope *
|
|
92
|
-
((initialVelocity +
|
|
93
|
-
dampingRatio * undampedAngularFreq * initialDelta) *
|
|
94
|
-
Math.sinh(freqForT) +
|
|
95
|
-
dampedAngularFreq *
|
|
96
|
-
initialDelta *
|
|
97
|
-
Math.cosh(freqForT))) /
|
|
98
|
-
dampedAngularFreq);
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
createSpring();
|
|
103
|
-
return {
|
|
104
|
-
next: (t) => {
|
|
105
|
-
const current = resolveSpring(t);
|
|
106
|
-
if (!isResolvedFromDuration) {
|
|
107
|
-
let currentVelocity = initialVelocity;
|
|
108
|
-
if (t !== 0) {
|
|
109
|
-
/**
|
|
110
|
-
* We only need to calculate velocity for under-damped springs
|
|
111
|
-
* as over- and critically-damped springs can't overshoot, so
|
|
112
|
-
* checking only for displacement is enough.
|
|
113
|
-
*/
|
|
114
|
-
if (dampingRatio < 1) {
|
|
115
|
-
const prevT = Math.max(0, t - velocitySampleDuration);
|
|
116
|
-
currentVelocity = velocityPerSecond(current - resolveSpring(prevT), t - prevT);
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
currentVelocity = 0;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
const isBelowVelocityThreshold = Math.abs(currentVelocity) <= restSpeed;
|
|
123
|
-
const isBelowDisplacementThreshold = Math.abs(target - current) <= restDelta;
|
|
124
|
-
state.done =
|
|
125
|
-
isBelowVelocityThreshold && isBelowDisplacementThreshold;
|
|
126
|
-
}
|
|
127
|
-
else {
|
|
128
|
-
state.done = t >= duration;
|
|
129
|
-
}
|
|
130
|
-
state.value = state.done ? target : current;
|
|
131
|
-
return state;
|
|
132
|
-
},
|
|
133
|
-
flipTarget: () => {
|
|
134
|
-
initialVelocity = -initialVelocity;
|
|
135
|
-
[origin, target] = [target, origin];
|
|
136
|
-
createSpring();
|
|
137
|
-
},
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
spring.needsInterpolation = (a, b) => typeof a === "string" || typeof b === "string";
|
|
141
|
-
const zero = (_t) => 0;
|
|
142
|
-
|
|
143
|
-
export { spring };
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Detect and load appropriate clock setting for the execution environment
|
|
3
|
-
*/
|
|
4
|
-
const defaultTimestep = (1 / 60) * 1000;
|
|
5
|
-
const getCurrentTime = typeof performance !== "undefined"
|
|
6
|
-
? () => performance.now()
|
|
7
|
-
: () => Date.now();
|
|
8
|
-
const onNextFrame = typeof window !== "undefined"
|
|
9
|
-
? (callback) => window.requestAnimationFrame(callback)
|
|
10
|
-
: (callback) => setTimeout(() => callback(getCurrentTime()), defaultTimestep);
|
|
11
|
-
|
|
12
|
-
export { defaultTimestep, onNextFrame };
|