framer-motion 7.7.3 → 7.8.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.
@@ -72,7 +72,7 @@ function animate({ from, autoplay = true, driver = framesync, elapsed = 0, repea
72
72
  latest = interpolateFromNumber(latest);
73
73
  isComplete = isForwardPlayback ? state.done : elapsed <= 0;
74
74
  }
75
- onUpdate === null || onUpdate === void 0 ? void 0 : onUpdate(latest);
75
+ onUpdate && onUpdate(latest);
76
76
  if (isComplete) {
77
77
  if (repeatCount === 0) {
78
78
  computedDuration =
@@ -87,14 +87,14 @@ function animate({ from, autoplay = true, driver = framesync, elapsed = 0, repea
87
87
  }
88
88
  }
89
89
  function play() {
90
- onPlay === null || onPlay === void 0 ? void 0 : onPlay();
90
+ onPlay && onPlay();
91
91
  driverControls = driver(update);
92
92
  driverControls.start();
93
93
  }
94
94
  autoplay && play();
95
95
  return {
96
96
  stop: () => {
97
- onStop === null || onStop === void 0 ? void 0 : onStop();
97
+ onStop && onStop();
98
98
  driverControls.stop();
99
99
  },
100
100
  };
@@ -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,6 +29,7 @@ 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
  */
@@ -39,11 +41,10 @@ function spring({ from = 0.0, to = 1.0, restSpeed = 2, restDelta = 0.01, ...opti
39
41
  const state = { done: false, value: from };
40
42
  let { stiffness, damping, mass, velocity, duration, isResolvedFromDuration, } = getSpringOptions(options);
41
43
  let resolveSpring = zero;
42
- let resolveVelocity = zero;
44
+ let initialVelocity = velocity ? -(velocity / 1000) : 0.0;
45
+ const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass));
43
46
  function createSpring() {
44
- const initialVelocity = velocity ? -(velocity / 1000) : 0.0;
45
47
  const initialDelta = to - from;
46
- const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass));
47
48
  const undampedAngularFreq = Math.sqrt(stiffness / mass) / 1000;
48
49
  /**
49
50
  * If we're working within what looks like a 0-1 range, change the default restDelta
@@ -65,29 +66,6 @@ function spring({ from = 0.0, to = 1.0, restSpeed = 2, restDelta = 0.01, ...opti
65
66
  Math.sin(angularFreq * t) +
66
67
  initialDelta * Math.cos(angularFreq * t)));
67
68
  };
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
69
  }
92
70
  else if (dampingRatio === 1) {
93
71
  // Critically damped spring
@@ -121,7 +99,21 @@ function spring({ from = 0.0, to = 1.0, restSpeed = 2, restDelta = 0.01, ...opti
121
99
  next: (t) => {
122
100
  const current = resolveSpring(t);
123
101
  if (!isResolvedFromDuration) {
124
- const currentVelocity = resolveVelocity(t) * 1000;
102
+ let currentVelocity = initialVelocity;
103
+ if (t !== 0) {
104
+ /**
105
+ * We only need to calculate velocity for under-damped springs
106
+ * as over- and critically-damped springs can't overshoot, so
107
+ * checking only for displacement is enough.
108
+ */
109
+ if (dampingRatio < 1) {
110
+ const prevT = Math.max(0, t - velocitySampleDuration);
111
+ currentVelocity = velocityPerSecond(current - resolveSpring(prevT), t - prevT);
112
+ }
113
+ else {
114
+ currentVelocity = 0;
115
+ }
116
+ }
125
117
  const isBelowVelocityThreshold = Math.abs(currentVelocity) <= restSpeed;
126
118
  const isBelowDisplacementThreshold = Math.abs(to - current) <= restDelta;
127
119
  state.done =
@@ -134,7 +126,7 @@ function spring({ from = 0.0, to = 1.0, restSpeed = 2, restDelta = 0.01, ...opti
134
126
  return state;
135
127
  },
136
128
  flipTarget: () => {
137
- velocity = -velocity;
129
+ initialVelocity = -initialVelocity;
138
130
  [from, to] = [to, from];
139
131
  createSpring();
140
132
  },
@@ -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 };
@@ -0,0 +1,3 @@
1
+ const appearStoreId = (id, value) => `${id}: ${value}`;
2
+
3
+ export { appearStoreId };
@@ -6,9 +6,9 @@ import { warning } from 'hey-listen';
6
6
  import { getAnimatableNone } from '../../render/dom/value-types/animatable-none.mjs';
7
7
  import { instantAnimationState } from '../../utils/use-instant-transition-state.mjs';
8
8
  import { resolveFinalValueInKeyframes } from '../../utils/resolve-value.mjs';
9
- import { delay } from '../../utils/delay.mjs';
10
9
  import { inertia } from '../legacy-popmotion/inertia.mjs';
11
10
  import { animate } from '../legacy-popmotion/index.mjs';
11
+ import { delay } from '../../utils/delay.mjs';
12
12
 
13
13
  /**
14
14
  * Decide whether a transition is defined on a given Transition.
@@ -96,6 +96,9 @@ function getPopmotionAnimationOptions(transition, options, key) {
96
96
  */
97
97
  function getAnimation(key, value, target, transition, onComplete) {
98
98
  const valueTransition = getValueTransition(transition, key) || {};
99
+ const { elapsed = 0 } = transition;
100
+ valueTransition.elapsed =
101
+ elapsed - secondsToMilliseconds(transition.delay || 0);
99
102
  let origin = valueTransition.from !== undefined ? valueTransition.from : value.get();
100
103
  const isTargetAnimatable = isAnimatable(key, target);
101
104
  if (origin === "none" && isTargetAnimatable && typeof target === "string") {
@@ -123,20 +126,23 @@ function getAnimation(key, value, target, transition, onComplete) {
123
126
  onComplete,
124
127
  onUpdate: (v) => value.set(v),
125
128
  };
126
- return valueTransition.type === "inertia" ||
129
+ const animation = valueTransition.type === "inertia" ||
127
130
  valueTransition.type === "decay"
128
131
  ? inertia({ ...options, ...valueTransition })
129
132
  : animate({
130
133
  ...getPopmotionAnimationOptions(valueTransition, options, key),
131
134
  onUpdate: (v) => {
132
135
  options.onUpdate(v);
133
- valueTransition.onUpdate && valueTransition.onUpdate(v);
136
+ valueTransition.onUpdate &&
137
+ valueTransition.onUpdate(v);
134
138
  },
135
139
  onComplete: () => {
136
140
  options.onComplete();
137
- valueTransition.onComplete && valueTransition.onComplete();
141
+ valueTransition.onComplete &&
142
+ valueTransition.onComplete();
138
143
  },
139
144
  });
145
+ return () => animation.stop();
140
146
  }
141
147
  function set() {
142
148
  const finalTarget = resolveFinalValueInKeyframes(target);
@@ -144,13 +150,16 @@ function getAnimation(key, value, target, transition, onComplete) {
144
150
  onComplete();
145
151
  valueTransition.onUpdate && valueTransition.onUpdate(finalTarget);
146
152
  valueTransition.onComplete && valueTransition.onComplete();
147
- return { stop: () => { } };
153
+ return () => { };
148
154
  }
149
- return !isOriginAnimatable ||
155
+ const useInstantAnimation = !isOriginAnimatable ||
150
156
  !isTargetAnimatable ||
151
- valueTransition.type === false
152
- ? set
153
- : start;
157
+ valueTransition.type === false;
158
+ return useInstantAnimation
159
+ ? valueTransition.elapsed
160
+ ? () => delay(set, -valueTransition.elapsed)
161
+ : set()
162
+ : start();
154
163
  }
155
164
  function isZero(value) {
156
165
  return (value === 0 ||
@@ -175,21 +184,7 @@ function startAnimation(key, value, target, transition = {}) {
175
184
  transition = { type: false };
176
185
  }
177
186
  return value.start((onComplete) => {
178
- let controls;
179
- const animation = getAnimation(key, value, target, transition, onComplete);
180
- const delayBy = getDelayFromTransition(transition, key);
181
- const start = () => (controls = animation());
182
- let cancelDelay;
183
- if (delayBy) {
184
- cancelDelay = delay(start, secondsToMilliseconds(delayBy));
185
- }
186
- else {
187
- start();
188
- }
189
- return () => {
190
- cancelDelay && cancelDelay();
191
- controls && controls.stop();
192
- };
187
+ return getAnimation(key, value, target, { ...transition, delay: getDelayFromTransition(transition, key) }, onComplete);
193
188
  });
194
189
  }
195
190
 
@@ -0,0 +1,3 @@
1
+ const cubicBezierAsString = ([a, b, c, d]) => `cubic-bezier(${a}, ${b}, ${c}, ${d})`;
2
+
3
+ export { cubicBezierAsString };
@@ -0,0 +1,16 @@
1
+ import { supports } from './supports.mjs';
2
+ import { cubicBezierAsString } from './easing.mjs';
3
+
4
+ function animateStyle(element, valueName, keyframes, { delay, duration, ease }) {
5
+ if (!supports.waapi())
6
+ return undefined;
7
+ const animation = element.animate({ [valueName]: keyframes }, {
8
+ delay,
9
+ duration,
10
+ easing: Array.isArray(ease) ? cubicBezierAsString(ease) : ease,
11
+ fill: "both",
12
+ });
13
+ return animation;
14
+ }
15
+
16
+ export { animateStyle };
@@ -0,0 +1,17 @@
1
+ const featureTests = {
2
+ waapi: () => Object.hasOwnProperty.call(Element.prototype, "animate"),
3
+ };
4
+ const results = {};
5
+ const supports = {};
6
+ /**
7
+ * Generate features tests that cache their results.
8
+ */
9
+ for (const key in featureTests) {
10
+ supports[key] = () => {
11
+ if (results[key] === undefined)
12
+ results[key] = featureTests[key]();
13
+ return results[key];
14
+ };
15
+ }
16
+
17
+ export { supports };
package/dist/es/index.mjs CHANGED
@@ -49,6 +49,9 @@ export { distance, distance2D } from './utils/distance.mjs';
49
49
  export { mix } from './utils/mix.mjs';
50
50
  export { pipe } from './utils/pipe.mjs';
51
51
  export { wrap } from './utils/wrap.mjs';
52
+ export { startOptimizedAppearAnimation } from './animation/optimized-appear/start.mjs';
53
+ export { optimizedAppearDataAttribute } from './animation/optimized-appear/data-id.mjs';
54
+ export { spring } from './animation/legacy-popmotion/spring.mjs';
52
55
  export { MotionContext, useVisualElementContext } from './context/MotionContext/index.mjs';
53
56
  export { MotionConfigContext } from './context/MotionConfigContext.mjs';
54
57
  export { PresenceContext } from './context/PresenceContext.mjs';
@@ -3,6 +3,8 @@ import { setTarget } from './setters.mjs';
3
3
  import { resolveVariant } from './resolve-dynamic-variants.mjs';
4
4
  import { transformProps } from '../html/utils/transform.mjs';
5
5
  import { isWillChangeMotionValue } from '../../value/use-will-change/is.mjs';
6
+ import { handoffOptimizedAppearAnimation } from '../../animation/optimized-appear/handoff.mjs';
7
+ import { optimizedAppearDataAttribute } from '../../animation/optimized-appear/data-id.mjs';
6
8
 
7
9
  function animateVisualElement(visualElement, definition, options = {}) {
8
10
  visualElement.notify("AnimationStart", definition);
@@ -81,7 +83,7 @@ function animateTarget(visualElement, definition, { delay = 0, transitionOverrid
81
83
  shouldBlockAnimation(animationTypeState, key))) {
82
84
  continue;
83
85
  }
84
- let valueTransition = { delay, ...transition };
86
+ let valueTransition = { delay, elapsed: 0, ...transition };
85
87
  /**
86
88
  * Make animation instant if this is a transform prop and we should reduce motion.
87
89
  */
@@ -92,6 +94,16 @@ function animateTarget(visualElement, definition, { delay = 0, transitionOverrid
92
94
  delay: 0,
93
95
  };
94
96
  }
97
+ /**
98
+ * If this is the first time a value is being animated, check
99
+ * to see if we're handling off from an existing animation.
100
+ */
101
+ if (!value.hasAnimated) {
102
+ const appearId = visualElement.getProps()[optimizedAppearDataAttribute];
103
+ if (appearId) {
104
+ valueTransition.elapsed = handoffOptimizedAppearAnimation(appearId, key);
105
+ }
106
+ }
95
107
  let animation = startAnimation(key, value, valueTarget, valueTransition);
96
108
  if (isWillChangeMotionValue(willChange)) {
97
109
  willChange.add(key);
@@ -22,7 +22,7 @@ function updateMotionValuesFromProps(element, next, prev) {
22
22
  * and warn against mismatches.
23
23
  */
24
24
  if (process.env.NODE_ENV === "development") {
25
- warnOnce(nextValue.version === "7.7.3", `Attempting to mix Framer Motion versions ${nextValue.version} with 7.7.3 may not work as expected.`);
25
+ warnOnce(nextValue.version === "7.8.0", `Attempting to mix Framer Motion versions ${nextValue.version} with 7.8.0 may not work as expected.`);
26
26
  }
27
27
  }
28
28
  else if (isMotionValue(prevValue)) {
@@ -1,5 +1,8 @@
1
1
  import { sync, cancelSync } from '../frameloop/index.mjs';
2
2
 
3
+ /**
4
+ * Timeout defined in ms
5
+ */
3
6
  function delay(callback, timeout) {
4
7
  const start = performance.now();
5
8
  const checkElapsed = ({ timestamp }) => {
@@ -25,7 +25,7 @@ class MotionValue {
25
25
  * This will be replaced by the build step with the latest version number.
26
26
  * When MotionValues are provided to motion components, warn if versions are mixed.
27
27
  */
28
- this.version = "7.7.3";
28
+ this.version = "7.8.0";
29
29
  /**
30
30
  * Duration, in milliseconds, since last updating frame.
31
31
  *