framer-motion 10.2.3 → 10.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/dom-entry.js +1 -1
- package/dist/cjs/index.js +18 -20
- package/dist/cjs/{wrap-27fda06a.js → wrap-62da7859.js} +456 -437
- package/dist/dom-entry.d.ts +485 -37
- package/dist/es/animation/GroupPlaybackControls.mjs +25 -0
- package/dist/es/animation/animate.mjs +2 -3
- package/dist/es/animation/create-instant-animation.mjs +13 -3
- package/dist/es/animation/generators/inertia.mjs +87 -0
- package/dist/es/animation/{legacy-popmotion → generators}/keyframes.mjs +8 -15
- package/dist/es/animation/{legacy-popmotion/find-spring.mjs → generators/spring/find.mjs} +6 -5
- package/dist/es/animation/generators/spring/index.mjs +129 -0
- package/dist/es/animation/generators/utils/velocity.mjs +9 -0
- package/dist/es/animation/index.mjs +2 -10
- package/dist/es/animation/js/driver-frameloop.mjs +12 -0
- package/dist/es/animation/js/index.mjs +206 -0
- package/dist/es/animation/optimized-appear/handoff.mjs +3 -1
- package/dist/es/animation/waapi/create-accelerated-animation.mjs +16 -10
- package/dist/es/frameloop/index.mjs +3 -4
- package/dist/es/gestures/pan/PanSession.mjs +2 -2
- package/dist/es/index.mjs +2 -3
- package/dist/es/render/utils/motion-values.mjs +1 -1
- package/dist/es/utils/time-conversion.mjs +2 -1
- package/dist/es/value/index.mjs +3 -3
- package/dist/es/value/use-spring.mjs +1 -1
- package/dist/es/value/use-velocity.mjs +4 -6
- package/dist/framer-motion.dev.js +475 -458
- package/dist/framer-motion.js +1 -1
- package/dist/index.d.ts +175 -218
- package/dist/projection.dev.js +5849 -5830
- package/dist/three-entry.d.ts +62 -63
- package/package.json +7 -11
- package/dist/es/animation/legacy-popmotion/decay.mjs +0 -34
- package/dist/es/animation/legacy-popmotion/index.mjs +0 -163
- package/dist/es/animation/legacy-popmotion/inertia.mjs +0 -90
- package/dist/es/animation/legacy-popmotion/spring.mjs +0 -143
- package/dist/es/frameloop/on-next-frame.mjs +0 -12
package/dist/three-entry.d.ts
CHANGED
|
@@ -856,9 +856,70 @@ interface CustomValueType {
|
|
|
856
856
|
* @public
|
|
857
857
|
*/
|
|
858
858
|
interface AnimationPlaybackControls {
|
|
859
|
-
currentTime
|
|
859
|
+
currentTime: number;
|
|
860
860
|
stop: () => void;
|
|
861
861
|
}
|
|
862
|
+
/**
|
|
863
|
+
* @public
|
|
864
|
+
*/
|
|
865
|
+
declare type ControlsAnimationDefinition = string | string[] | TargetAndTransition | TargetResolver;
|
|
866
|
+
/**
|
|
867
|
+
* @public
|
|
868
|
+
*/
|
|
869
|
+
interface AnimationControls {
|
|
870
|
+
/**
|
|
871
|
+
* Starts an animation on all linked components.
|
|
872
|
+
*
|
|
873
|
+
* @remarks
|
|
874
|
+
*
|
|
875
|
+
* ```jsx
|
|
876
|
+
* controls.start("variantLabel")
|
|
877
|
+
* controls.start({
|
|
878
|
+
* x: 0,
|
|
879
|
+
* transition: { duration: 1 }
|
|
880
|
+
* })
|
|
881
|
+
* ```
|
|
882
|
+
*
|
|
883
|
+
* @param definition - Properties or variant label to animate to
|
|
884
|
+
* @param transition - Optional `transtion` to apply to a variant
|
|
885
|
+
* @returns - A `Promise` that resolves when all animations have completed.
|
|
886
|
+
*
|
|
887
|
+
* @public
|
|
888
|
+
*/
|
|
889
|
+
start(definition: ControlsAnimationDefinition, transitionOverride?: Transition): Promise<any>;
|
|
890
|
+
/**
|
|
891
|
+
* Instantly set to a set of properties or a variant.
|
|
892
|
+
*
|
|
893
|
+
* ```jsx
|
|
894
|
+
* // With properties
|
|
895
|
+
* controls.set({ opacity: 0 })
|
|
896
|
+
*
|
|
897
|
+
* // With variants
|
|
898
|
+
* controls.set("hidden")
|
|
899
|
+
* ```
|
|
900
|
+
*
|
|
901
|
+
* @privateRemarks
|
|
902
|
+
* We could perform a similar trick to `.start` where this can be called before mount
|
|
903
|
+
* and we maintain a list of of pending actions that get applied on mount. But the
|
|
904
|
+
* expectation of `set` is that it happens synchronously and this would be difficult
|
|
905
|
+
* to do before any children have even attached themselves. It's also poor practise
|
|
906
|
+
* and we should discourage render-synchronous `.start` calls rather than lean into this.
|
|
907
|
+
*
|
|
908
|
+
* @public
|
|
909
|
+
*/
|
|
910
|
+
set(definition: ControlsAnimationDefinition): void;
|
|
911
|
+
/**
|
|
912
|
+
* Stops animations on all linked components.
|
|
913
|
+
*
|
|
914
|
+
* ```jsx
|
|
915
|
+
* controls.stop()
|
|
916
|
+
* ```
|
|
917
|
+
*
|
|
918
|
+
* @public
|
|
919
|
+
*/
|
|
920
|
+
stop(): void;
|
|
921
|
+
mount(): () => void;
|
|
922
|
+
}
|
|
862
923
|
|
|
863
924
|
/**
|
|
864
925
|
* @public
|
|
@@ -1014,68 +1075,6 @@ declare class MotionValue<V = any> {
|
|
|
1014
1075
|
destroy(): void;
|
|
1015
1076
|
}
|
|
1016
1077
|
|
|
1017
|
-
/**
|
|
1018
|
-
* @public
|
|
1019
|
-
*/
|
|
1020
|
-
declare type ControlsAnimationDefinition = string | string[] | TargetAndTransition | TargetResolver;
|
|
1021
|
-
/**
|
|
1022
|
-
* @public
|
|
1023
|
-
*/
|
|
1024
|
-
interface AnimationControls {
|
|
1025
|
-
/**
|
|
1026
|
-
* Starts an animation on all linked components.
|
|
1027
|
-
*
|
|
1028
|
-
* @remarks
|
|
1029
|
-
*
|
|
1030
|
-
* ```jsx
|
|
1031
|
-
* controls.start("variantLabel")
|
|
1032
|
-
* controls.start({
|
|
1033
|
-
* x: 0,
|
|
1034
|
-
* transition: { duration: 1 }
|
|
1035
|
-
* })
|
|
1036
|
-
* ```
|
|
1037
|
-
*
|
|
1038
|
-
* @param definition - Properties or variant label to animate to
|
|
1039
|
-
* @param transition - Optional `transtion` to apply to a variant
|
|
1040
|
-
* @returns - A `Promise` that resolves when all animations have completed.
|
|
1041
|
-
*
|
|
1042
|
-
* @public
|
|
1043
|
-
*/
|
|
1044
|
-
start(definition: ControlsAnimationDefinition, transitionOverride?: Transition): Promise<any>;
|
|
1045
|
-
/**
|
|
1046
|
-
* Instantly set to a set of properties or a variant.
|
|
1047
|
-
*
|
|
1048
|
-
* ```jsx
|
|
1049
|
-
* // With properties
|
|
1050
|
-
* controls.set({ opacity: 0 })
|
|
1051
|
-
*
|
|
1052
|
-
* // With variants
|
|
1053
|
-
* controls.set("hidden")
|
|
1054
|
-
* ```
|
|
1055
|
-
*
|
|
1056
|
-
* @privateRemarks
|
|
1057
|
-
* We could perform a similar trick to `.start` where this can be called before mount
|
|
1058
|
-
* and we maintain a list of of pending actions that get applied on mount. But the
|
|
1059
|
-
* expectation of `set` is that it happens synchronously and this would be difficult
|
|
1060
|
-
* to do before any children have even attached themselves. It's also poor practise
|
|
1061
|
-
* and we should discourage render-synchronous `.start` calls rather than lean into this.
|
|
1062
|
-
*
|
|
1063
|
-
* @public
|
|
1064
|
-
*/
|
|
1065
|
-
set(definition: ControlsAnimationDefinition): void;
|
|
1066
|
-
/**
|
|
1067
|
-
* Stops animations on all linked components.
|
|
1068
|
-
*
|
|
1069
|
-
* ```jsx
|
|
1070
|
-
* controls.stop()
|
|
1071
|
-
* ```
|
|
1072
|
-
*
|
|
1073
|
-
* @public
|
|
1074
|
-
*/
|
|
1075
|
-
stop(): void;
|
|
1076
|
-
mount(): () => void;
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
1078
|
interface RefObject<T> {
|
|
1080
1079
|
current: T | null;
|
|
1081
1080
|
}
|
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,15 +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.
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
"path": "./dist/size-rollup-animate.js",
|
|
104
|
-
"maxSize": "10 kB"
|
|
100
|
+
"maxSize": "25.6 kB"
|
|
105
101
|
},
|
|
106
102
|
{
|
|
107
103
|
"path": "./dist/size-rollup-animate.js",
|
|
@@ -113,12 +109,12 @@
|
|
|
113
109
|
},
|
|
114
110
|
{
|
|
115
111
|
"path": "./dist/size-webpack-dom-animation.js",
|
|
116
|
-
"maxSize": "18.
|
|
112
|
+
"maxSize": "18.56 kB"
|
|
117
113
|
},
|
|
118
114
|
{
|
|
119
115
|
"path": "./dist/size-webpack-dom-max.js",
|
|
120
|
-
"maxSize": "30.
|
|
116
|
+
"maxSize": "30.42 kB"
|
|
121
117
|
}
|
|
122
118
|
],
|
|
123
|
-
"gitHead": "
|
|
119
|
+
"gitHead": "82eb0ba26cd103d524a27dc95415e437cce35822"
|
|
124
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 };
|