DOMKeyframesResolver.mjs 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. import { isNone } from '../../animation/utils/is-none.mjs';
  2. import { positionalKeys } from '../html/utils/keys-position.mjs';
  3. import { makeNoneKeyframesAnimatable } from '../html/utils/make-none-animatable.mjs';
  4. import { KeyframeResolver } from '../utils/KeyframesResolver.mjs';
  5. import { getVariableValue } from './utils/css-variables-conversion.mjs';
  6. import { isCSSVariableToken } from './utils/is-css-variable.mjs';
  7. import { isNumOrPxType, positionalValues } from './utils/unit-conversion.mjs';
  8. import { findDimensionValueType } from './value-types/dimensions.mjs';
  9. class DOMKeyframesResolver extends KeyframeResolver {
  10. constructor(unresolvedKeyframes, onComplete, name, motionValue, element) {
  11. super(unresolvedKeyframes, onComplete, name, motionValue, element, true);
  12. }
  13. readKeyframes() {
  14. const { unresolvedKeyframes, element, name } = this;
  15. if (!element || !element.current)
  16. return;
  17. super.readKeyframes();
  18. /**
  19. * If any keyframe is a CSS variable, we need to find its value by sampling the element
  20. */
  21. for (let i = 0; i < unresolvedKeyframes.length; i++) {
  22. let keyframe = unresolvedKeyframes[i];
  23. if (typeof keyframe === "string") {
  24. keyframe = keyframe.trim();
  25. if (isCSSVariableToken(keyframe)) {
  26. const resolved = getVariableValue(keyframe, element.current);
  27. if (resolved !== undefined) {
  28. unresolvedKeyframes[i] = resolved;
  29. }
  30. if (i === unresolvedKeyframes.length - 1) {
  31. this.finalKeyframe = keyframe;
  32. }
  33. }
  34. }
  35. }
  36. /**
  37. * Resolve "none" values. We do this potentially twice - once before and once after measuring keyframes.
  38. * This could be seen as inefficient but it's a trade-off to avoid measurements in more situations, which
  39. * have a far bigger performance impact.
  40. */
  41. this.resolveNoneKeyframes();
  42. /**
  43. * Check to see if unit type has changed. If so schedule jobs that will
  44. * temporarily set styles to the destination keyframes.
  45. * Skip if we have more than two keyframes or this isn't a positional value.
  46. * TODO: We can throw if there are multiple keyframes and the value type changes.
  47. */
  48. if (!positionalKeys.has(name) || unresolvedKeyframes.length !== 2) {
  49. return;
  50. }
  51. const [origin, target] = unresolvedKeyframes;
  52. const originType = findDimensionValueType(origin);
  53. const targetType = findDimensionValueType(target);
  54. /**
  55. * Either we don't recognise these value types or we can animate between them.
  56. */
  57. if (originType === targetType)
  58. return;
  59. /**
  60. * If both values are numbers or pixels, we can animate between them by
  61. * converting them to numbers.
  62. */
  63. if (isNumOrPxType(originType) && isNumOrPxType(targetType)) {
  64. for (let i = 0; i < unresolvedKeyframes.length; i++) {
  65. const value = unresolvedKeyframes[i];
  66. if (typeof value === "string") {
  67. unresolvedKeyframes[i] = parseFloat(value);
  68. }
  69. }
  70. }
  71. else {
  72. /**
  73. * Else, the only way to resolve this is by measuring the element.
  74. */
  75. this.needsMeasurement = true;
  76. }
  77. }
  78. resolveNoneKeyframes() {
  79. const { unresolvedKeyframes, name } = this;
  80. const noneKeyframeIndexes = [];
  81. for (let i = 0; i < unresolvedKeyframes.length; i++) {
  82. if (isNone(unresolvedKeyframes[i])) {
  83. noneKeyframeIndexes.push(i);
  84. }
  85. }
  86. if (noneKeyframeIndexes.length) {
  87. makeNoneKeyframesAnimatable(unresolvedKeyframes, noneKeyframeIndexes, name);
  88. }
  89. }
  90. measureInitialState() {
  91. const { element, unresolvedKeyframes, name } = this;
  92. if (!element || !element.current)
  93. return;
  94. if (name === "height") {
  95. this.suspendedScrollY = window.pageYOffset;
  96. }
  97. this.measuredOrigin = positionalValues[name](element.measureViewportBox(), window.getComputedStyle(element.current));
  98. unresolvedKeyframes[0] = this.measuredOrigin;
  99. // Set final key frame to measure after next render
  100. const measureKeyframe = unresolvedKeyframes[unresolvedKeyframes.length - 1];
  101. if (measureKeyframe !== undefined) {
  102. element.getValue(name, measureKeyframe).jump(measureKeyframe, false);
  103. }
  104. }
  105. measureEndState() {
  106. const { element, name, unresolvedKeyframes } = this;
  107. if (!element || !element.current)
  108. return;
  109. const value = element.getValue(name);
  110. value && value.jump(this.measuredOrigin, false);
  111. const finalKeyframeIndex = unresolvedKeyframes.length - 1;
  112. const finalKeyframe = unresolvedKeyframes[finalKeyframeIndex];
  113. unresolvedKeyframes[finalKeyframeIndex] = positionalValues[name](element.measureViewportBox(), window.getComputedStyle(element.current));
  114. if (finalKeyframe !== null && this.finalKeyframe === undefined) {
  115. this.finalKeyframe = finalKeyframe;
  116. }
  117. // If we removed transform values, reapply them before the next render
  118. if (this.removedTransforms?.length) {
  119. this.removedTransforms.forEach(([unsetTransformName, unsetTransformValue]) => {
  120. element
  121. .getValue(unsetTransformName)
  122. .set(unsetTransformValue);
  123. });
  124. }
  125. this.resolveNoneKeyframes();
  126. }
  127. }
  128. export { DOMKeyframesResolver };