@solid-primitives/scroll
Version:
Reactive primitives to react to element/window scrolling.
85 lines (84 loc) • 3.28 kB
JavaScript
import { createEventListener } from "@solid-primitives/event-listener";
import { createHydratableSingletonRoot } from "@solid-primitives/rootless";
import { createDerivedStaticStore } from "@solid-primitives/static-store";
import { createSignal, onMount, sharedConfig } from "solid-js";
import { isServer } from "solid-js/web";
export function getScrollParent(node) {
if (isServer) {
return {};
}
while (node && !isScrollable(node)) {
node = node.parentElement;
}
return node || document.scrollingElement || document.documentElement;
}
export function isScrollable(node) {
if (isServer) {
return false;
}
const style = window.getComputedStyle(node);
return /(auto|scroll)/.test(style.overflow + style.overflowX + style.overflowY);
}
const FALLBACK_SCROLL_POSITION = { x: 0, y: 0 };
/**
* Get an `{ x: number, y: number }` object of element/window scroll position.
*/
export function getScrollPosition(target) {
if (isServer || !target) {
return { ...FALLBACK_SCROLL_POSITION };
}
if (target instanceof Window)
return {
x: target.scrollX,
y: target.scrollY,
};
return {
x: target.scrollLeft,
y: target.scrollTop,
};
}
/**
* Reactive primitive providing a store-like object with current scroll position of specified target.
* @param target element/window to listen to scroll events. can be a reactive singal.
* @returns a store-like reactive object `{ x: number, y: number }` of current scroll position of {@link target}
* @example
* // target will be window by default
* const windowScroll = createScrollPosition();
*
* createEffect(() => {
* // returned object is a reactive store-like structure
* windowScroll.x; // => number
* windowScroll.y; // => number
* });
*/
export function createScrollPosition(target) {
if (isServer) {
return FALLBACK_SCROLL_POSITION;
}
target = target || window;
const isFn = typeof target === "function", isHydrating = sharedConfig.context, getTargetPos = isFn
? () => getScrollPosition(target())
: () => getScrollPosition(target),
// changing the calc signal will trigger the derived store to update
[calc, setCalc] = createSignal(isHydrating ? () => FALLBACK_SCROLL_POSITION : getTargetPos, {
equals: false,
}), trigger = () => setCalc(() => getTargetPos), pos = createDerivedStaticStore(() => calc()());
// update the position on mount if we are hydrating (initial pos is null)
// or if target is a function (which means it could be a ref that will be populated onMount)
if (isHydrating || isFn)
onMount(trigger);
createEventListener(target, "scroll", trigger, { passive: true });
return pos;
}
/**
* Returns a reactive object with current window scroll position.
*
* This is a [singleton root](https://github.com/solidjs-community/solid-primitives/tree/main/packages/rootless#createSingletonRoot) primitive.
*
* @example
* const scroll = useWindowScrollPosition();
* createEffect(() => {
* console.log(scroll.x, scroll.y)
* })
*/
export const useWindowScrollPosition = /*#__PURE__*/ createHydratableSingletonRoot(() => createScrollPosition(isServer ? () => undefined : window));