find.mjs 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. import { warning, secondsToMilliseconds, millisecondsToSeconds } from 'motion-utils';
  2. import { clamp } from '../../../utils/clamp.mjs';
  3. import { springDefaults } from './defaults.mjs';
  4. const safeMin = 0.001;
  5. function findSpring({ duration = springDefaults.duration, bounce = springDefaults.bounce, velocity = springDefaults.velocity, mass = springDefaults.mass, }) {
  6. let envelope;
  7. let derivative;
  8. warning(duration <= secondsToMilliseconds(springDefaults.maxDuration), "Spring duration must be 10 seconds or less");
  9. let dampingRatio = 1 - bounce;
  10. /**
  11. * Restrict dampingRatio and duration to within acceptable ranges.
  12. */
  13. dampingRatio = clamp(springDefaults.minDamping, springDefaults.maxDamping, dampingRatio);
  14. duration = clamp(springDefaults.minDuration, springDefaults.maxDuration, millisecondsToSeconds(duration));
  15. if (dampingRatio < 1) {
  16. /**
  17. * Underdamped spring
  18. */
  19. envelope = (undampedFreq) => {
  20. const exponentialDecay = undampedFreq * dampingRatio;
  21. const delta = exponentialDecay * duration;
  22. const a = exponentialDecay - velocity;
  23. const b = calcAngularFreq(undampedFreq, dampingRatio);
  24. const c = Math.exp(-delta);
  25. return safeMin - (a / b) * c;
  26. };
  27. derivative = (undampedFreq) => {
  28. const exponentialDecay = undampedFreq * dampingRatio;
  29. const delta = exponentialDecay * duration;
  30. const d = delta * velocity + velocity;
  31. const e = Math.pow(dampingRatio, 2) * Math.pow(undampedFreq, 2) * duration;
  32. const f = Math.exp(-delta);
  33. const g = calcAngularFreq(Math.pow(undampedFreq, 2), dampingRatio);
  34. const factor = -envelope(undampedFreq) + safeMin > 0 ? -1 : 1;
  35. return (factor * ((d - e) * f)) / g;
  36. };
  37. }
  38. else {
  39. /**
  40. * Critically-damped spring
  41. */
  42. envelope = (undampedFreq) => {
  43. const a = Math.exp(-undampedFreq * duration);
  44. const b = (undampedFreq - velocity) * duration + 1;
  45. return -safeMin + a * b;
  46. };
  47. derivative = (undampedFreq) => {
  48. const a = Math.exp(-undampedFreq * duration);
  49. const b = (velocity - undampedFreq) * (duration * duration);
  50. return a * b;
  51. };
  52. }
  53. const initialGuess = 5 / duration;
  54. const undampedFreq = approximateRoot(envelope, derivative, initialGuess);
  55. duration = secondsToMilliseconds(duration);
  56. if (isNaN(undampedFreq)) {
  57. return {
  58. stiffness: springDefaults.stiffness,
  59. damping: springDefaults.damping,
  60. duration,
  61. };
  62. }
  63. else {
  64. const stiffness = Math.pow(undampedFreq, 2) * mass;
  65. return {
  66. stiffness,
  67. damping: dampingRatio * 2 * Math.sqrt(mass * stiffness),
  68. duration,
  69. };
  70. }
  71. }
  72. const rootIterations = 12;
  73. function approximateRoot(envelope, derivative, initialGuess) {
  74. let result = initialGuess;
  75. for (let i = 1; i < rootIterations; i++) {
  76. result = result - envelope(result) / derivative(result);
  77. }
  78. return result;
  79. }
  80. function calcAngularFreq(undampedFreq, dampingRatio) {
  81. return undampedFreq * Math.sqrt(1 - dampingRatio * dampingRatio);
  82. }
  83. export { calcAngularFreq, findSpring };