UNPKG

@solid-primitives/scroll

Version:

Reactive primitives to react to element/window scrolling.

85 lines (84 loc) 3.28 kB
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));