motion-spring.tsx 1.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445
  1. import { attachSpring, motionValue } from "motion"
  2. import type { SpringOptions } from "motion"
  3. import { createEffect, createSignal, onCleanup } from "solid-js"
  4. type Opt = Partial<Pick<SpringOptions, "visualDuration" | "bounce" | "stiffness" | "damping" | "mass" | "velocity">>
  5. const eq = (a: Opt | undefined, b: Opt | undefined) =>
  6. a?.visualDuration === b?.visualDuration &&
  7. a?.bounce === b?.bounce &&
  8. a?.stiffness === b?.stiffness &&
  9. a?.damping === b?.damping &&
  10. a?.mass === b?.mass &&
  11. a?.velocity === b?.velocity
  12. export function useSpring(target: () => number, options?: Opt | (() => Opt)) {
  13. const read = () => (typeof options === "function" ? options() : options)
  14. const [value, setValue] = createSignal(target())
  15. const source = motionValue(value())
  16. const spring = motionValue(value())
  17. let config = read()
  18. let stop = attachSpring(spring, source, config)
  19. let off = spring.on("change", (next: number) => setValue(next))
  20. createEffect(() => {
  21. source.set(target())
  22. })
  23. createEffect(() => {
  24. if (!options) return
  25. const next = read()
  26. if (eq(config, next)) return
  27. config = next
  28. stop()
  29. stop = attachSpring(spring, source, next)
  30. setValue(spring.get())
  31. })
  32. onCleanup(() => {
  33. off()
  34. stop()
  35. spring.destroy()
  36. source.destroy()
  37. })
  38. return value
  39. }