track.mjs 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. import { frame, cancelFrame, frameData } from 'motion-dom';
  2. import { resize } from '../resize/index.mjs';
  3. import { createScrollInfo } from './info.mjs';
  4. import { createOnScrollHandler } from './on-scroll-handler.mjs';
  5. const scrollListeners = new WeakMap();
  6. const resizeListeners = new WeakMap();
  7. const onScrollHandlers = new WeakMap();
  8. const getEventTarget = (element) => element === document.documentElement ? window : element;
  9. function scrollInfo(onScroll, { container = document.documentElement, ...options } = {}) {
  10. let containerHandlers = onScrollHandlers.get(container);
  11. /**
  12. * Get the onScroll handlers for this container.
  13. * If one isn't found, create a new one.
  14. */
  15. if (!containerHandlers) {
  16. containerHandlers = new Set();
  17. onScrollHandlers.set(container, containerHandlers);
  18. }
  19. /**
  20. * Create a new onScroll handler for the provided callback.
  21. */
  22. const info = createScrollInfo();
  23. const containerHandler = createOnScrollHandler(container, onScroll, info, options);
  24. containerHandlers.add(containerHandler);
  25. /**
  26. * Check if there's a scroll event listener for this container.
  27. * If not, create one.
  28. */
  29. if (!scrollListeners.has(container)) {
  30. const measureAll = () => {
  31. for (const handler of containerHandlers)
  32. handler.measure();
  33. };
  34. const updateAll = () => {
  35. for (const handler of containerHandlers) {
  36. handler.update(frameData.timestamp);
  37. }
  38. };
  39. const notifyAll = () => {
  40. for (const handler of containerHandlers)
  41. handler.notify();
  42. };
  43. const listener = () => {
  44. frame.read(measureAll, false, true);
  45. frame.read(updateAll, false, true);
  46. frame.update(notifyAll, false, true);
  47. };
  48. scrollListeners.set(container, listener);
  49. const target = getEventTarget(container);
  50. window.addEventListener("resize", listener, { passive: true });
  51. if (container !== document.documentElement) {
  52. resizeListeners.set(container, resize(container, listener));
  53. }
  54. target.addEventListener("scroll", listener, { passive: true });
  55. }
  56. const listener = scrollListeners.get(container);
  57. frame.read(listener, false, true);
  58. return () => {
  59. cancelFrame(listener);
  60. /**
  61. * Check if we even have any handlers for this container.
  62. */
  63. const currentHandlers = onScrollHandlers.get(container);
  64. if (!currentHandlers)
  65. return;
  66. currentHandlers.delete(containerHandler);
  67. if (currentHandlers.size)
  68. return;
  69. /**
  70. * If no more handlers, remove the scroll listener too.
  71. */
  72. const scrollListener = scrollListeners.get(container);
  73. scrollListeners.delete(container);
  74. if (scrollListener) {
  75. getEventTarget(container).removeEventListener("scroll", scrollListener);
  76. resizeListeners.get(container)?.();
  77. window.removeEventListener("resize", scrollListener);
  78. }
  79. };
  80. }
  81. export { scrollInfo };