mix-values.mjs 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. import { progress, noop } from 'motion-utils';
  2. import { circOut } from '../../easing/circ.mjs';
  3. import { mixNumber } from '../../utils/mix/number.mjs';
  4. import { percent, px } from '../../value/types/numbers/units.mjs';
  5. const borders = ["TopLeft", "TopRight", "BottomLeft", "BottomRight"];
  6. const numBorders = borders.length;
  7. const asNumber = (value) => typeof value === "string" ? parseFloat(value) : value;
  8. const isPx = (value) => typeof value === "number" || px.test(value);
  9. function mixValues(target, follow, lead, progress, shouldCrossfadeOpacity, isOnlyMember) {
  10. if (shouldCrossfadeOpacity) {
  11. target.opacity = mixNumber(0, lead.opacity ?? 1, easeCrossfadeIn(progress));
  12. target.opacityExit = mixNumber(follow.opacity ?? 1, 0, easeCrossfadeOut(progress));
  13. }
  14. else if (isOnlyMember) {
  15. target.opacity = mixNumber(follow.opacity ?? 1, lead.opacity ?? 1, progress);
  16. }
  17. /**
  18. * Mix border radius
  19. */
  20. for (let i = 0; i < numBorders; i++) {
  21. const borderLabel = `border${borders[i]}Radius`;
  22. let followRadius = getRadius(follow, borderLabel);
  23. let leadRadius = getRadius(lead, borderLabel);
  24. if (followRadius === undefined && leadRadius === undefined)
  25. continue;
  26. followRadius || (followRadius = 0);
  27. leadRadius || (leadRadius = 0);
  28. const canMix = followRadius === 0 ||
  29. leadRadius === 0 ||
  30. isPx(followRadius) === isPx(leadRadius);
  31. if (canMix) {
  32. target[borderLabel] = Math.max(mixNumber(asNumber(followRadius), asNumber(leadRadius), progress), 0);
  33. if (percent.test(leadRadius) || percent.test(followRadius)) {
  34. target[borderLabel] += "%";
  35. }
  36. }
  37. else {
  38. target[borderLabel] = leadRadius;
  39. }
  40. }
  41. /**
  42. * Mix rotation
  43. */
  44. if (follow.rotate || lead.rotate) {
  45. target.rotate = mixNumber(follow.rotate || 0, lead.rotate || 0, progress);
  46. }
  47. }
  48. function getRadius(values, radiusName) {
  49. return values[radiusName] !== undefined
  50. ? values[radiusName]
  51. : values.borderRadius;
  52. }
  53. // /**
  54. // * We only want to mix the background color if there's a follow element
  55. // * that we're not crossfading opacity between. For instance with switch
  56. // * AnimateSharedLayout animations, this helps the illusion of a continuous
  57. // * element being animated but also cuts down on the number of paints triggered
  58. // * for elements where opacity is doing that work for us.
  59. // */
  60. // if (
  61. // !hasFollowElement &&
  62. // latestLeadValues.backgroundColor &&
  63. // latestFollowValues.backgroundColor
  64. // ) {
  65. // /**
  66. // * This isn't ideal performance-wise as mixColor is creating a new function every frame.
  67. // * We could probably create a mixer that runs at the start of the animation but
  68. // * the idea behind the crossfader is that it runs dynamically between two potentially
  69. // * changing targets (ie opacity or borderRadius may be animating independently via variants)
  70. // */
  71. // leadState.backgroundColor = followState.backgroundColor = mixColor(
  72. // latestFollowValues.backgroundColor as string,
  73. // latestLeadValues.backgroundColor as string
  74. // )(p)
  75. // }
  76. const easeCrossfadeIn = /*@__PURE__*/ compress(0, 0.5, circOut);
  77. const easeCrossfadeOut = /*@__PURE__*/ compress(0.5, 0.95, noop);
  78. function compress(min, max, easing) {
  79. return (p) => {
  80. // Could replace ifs with clamp
  81. if (p < min)
  82. return 0;
  83. if (p > max)
  84. return 1;
  85. return easing(progress(min, max, p));
  86. };
  87. }
  88. export { mixValues };