framer-motion 7.7.3 → 7.8.1

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