81 lines
2.9 KiB
JavaScript
81 lines
2.9 KiB
JavaScript
import { resize, frame, cancelFrame, frameData } from 'motion-dom';
|
|
import { noop } from 'motion-utils';
|
|
import { createScrollInfo } from './info.mjs';
|
|
import { createOnScrollHandler } from './on-scroll-handler.mjs';
|
|
|
|
const scrollListeners = new WeakMap();
|
|
const resizeListeners = new WeakMap();
|
|
const onScrollHandlers = new WeakMap();
|
|
const getEventTarget = (element) => element === document.scrollingElement ? window : element;
|
|
function scrollInfo(onScroll, { container = document.scrollingElement, ...options } = {}) {
|
|
if (!container)
|
|
return noop;
|
|
let containerHandlers = onScrollHandlers.get(container);
|
|
/**
|
|
* Get the onScroll handlers for this container.
|
|
* If one isn't found, create a new one.
|
|
*/
|
|
if (!containerHandlers) {
|
|
containerHandlers = new Set();
|
|
onScrollHandlers.set(container, containerHandlers);
|
|
}
|
|
/**
|
|
* Create a new onScroll handler for the provided callback.
|
|
*/
|
|
const info = createScrollInfo();
|
|
const containerHandler = createOnScrollHandler(container, onScroll, info, options);
|
|
containerHandlers.add(containerHandler);
|
|
/**
|
|
* Check if there's a scroll event listener for this container.
|
|
* If not, create one.
|
|
*/
|
|
if (!scrollListeners.has(container)) {
|
|
const measureAll = () => {
|
|
for (const handler of containerHandlers) {
|
|
handler.measure(frameData.timestamp);
|
|
}
|
|
frame.preUpdate(notifyAll);
|
|
};
|
|
const notifyAll = () => {
|
|
for (const handler of containerHandlers) {
|
|
handler.notify();
|
|
}
|
|
};
|
|
const listener = () => frame.read(measureAll);
|
|
scrollListeners.set(container, listener);
|
|
const target = getEventTarget(container);
|
|
window.addEventListener("resize", listener, { passive: true });
|
|
if (container !== document.documentElement) {
|
|
resizeListeners.set(container, resize(container, listener));
|
|
}
|
|
target.addEventListener("scroll", listener, { passive: true });
|
|
listener();
|
|
}
|
|
const listener = scrollListeners.get(container);
|
|
frame.read(listener, false, true);
|
|
return () => {
|
|
cancelFrame(listener);
|
|
/**
|
|
* Check if we even have any handlers for this container.
|
|
*/
|
|
const currentHandlers = onScrollHandlers.get(container);
|
|
if (!currentHandlers)
|
|
return;
|
|
currentHandlers.delete(containerHandler);
|
|
if (currentHandlers.size)
|
|
return;
|
|
/**
|
|
* If no more handlers, remove the scroll listener too.
|
|
*/
|
|
const scrollListener = scrollListeners.get(container);
|
|
scrollListeners.delete(container);
|
|
if (scrollListener) {
|
|
getEventTarget(container).removeEventListener("scroll", scrollListener);
|
|
resizeListeners.get(container)?.();
|
|
window.removeEventListener("resize", scrollListener);
|
|
}
|
|
};
|
|
}
|
|
|
|
export { scrollInfo };
|