interpolate.mjs 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. import { invariant, noop, progress } from 'motion-utils';
  2. import { clamp } from './clamp.mjs';
  3. import { mix } from './mix/index.mjs';
  4. import { pipe } from './pipe.mjs';
  5. function createMixers(output, ease, customMixer) {
  6. const mixers = [];
  7. const mixerFactory = customMixer || mix;
  8. const numMixers = output.length - 1;
  9. for (let i = 0; i < numMixers; i++) {
  10. let mixer = mixerFactory(output[i], output[i + 1]);
  11. if (ease) {
  12. const easingFunction = Array.isArray(ease) ? ease[i] || noop : ease;
  13. mixer = pipe(easingFunction, mixer);
  14. }
  15. mixers.push(mixer);
  16. }
  17. return mixers;
  18. }
  19. /**
  20. * Create a function that maps from a numerical input array to a generic output array.
  21. *
  22. * Accepts:
  23. * - Numbers
  24. * - Colors (hex, hsl, hsla, rgb, rgba)
  25. * - Complex (combinations of one or more numbers or strings)
  26. *
  27. * ```jsx
  28. * const mixColor = interpolate([0, 1], ['#fff', '#000'])
  29. *
  30. * mixColor(0.5) // 'rgba(128, 128, 128, 1)'
  31. * ```
  32. *
  33. * TODO Revist this approach once we've moved to data models for values,
  34. * probably not needed to pregenerate mixer functions.
  35. *
  36. * @public
  37. */
  38. function interpolate(input, output, { clamp: isClamp = true, ease, mixer } = {}) {
  39. const inputLength = input.length;
  40. invariant(inputLength === output.length, "Both input and output ranges must be the same length");
  41. /**
  42. * If we're only provided a single input, we can just make a function
  43. * that returns the output.
  44. */
  45. if (inputLength === 1)
  46. return () => output[0];
  47. if (inputLength === 2 && output[0] === output[1])
  48. return () => output[1];
  49. const isZeroDeltaRange = input[0] === input[1];
  50. // If input runs highest -> lowest, reverse both arrays
  51. if (input[0] > input[inputLength - 1]) {
  52. input = [...input].reverse();
  53. output = [...output].reverse();
  54. }
  55. const mixers = createMixers(output, ease, mixer);
  56. const numMixers = mixers.length;
  57. const interpolator = (v) => {
  58. if (isZeroDeltaRange && v < input[0])
  59. return output[0];
  60. let i = 0;
  61. if (numMixers > 1) {
  62. for (; i < input.length - 2; i++) {
  63. if (v < input[i + 1])
  64. break;
  65. }
  66. }
  67. const progressInRange = progress(input[i], input[i + 1], v);
  68. return mixers[i](progressInRange);
  69. };
  70. return isClamp
  71. ? (v) => interpolator(clamp(input[0], input[inputLength - 1], v))
  72. : interpolator;
  73. }
  74. export { interpolate };