UNPKG

framer-motion

Version:

A simple and powerful JavaScript animation library

1 lines • 10.1 kB
{"version":3,"file":"auto-scroll.mjs","sources":["../../../../../src/components/Reorder/utils/auto-scroll.ts"],"sourcesContent":["import { frameData } from \"motion-dom\"\n\nconst threshold = 50\n// Pixels per millisecond (25px per frame at 60fps)\nconst maxSpeed = 1.5\n\nconst overflowStyles = new Set([\"auto\", \"scroll\"])\n\ntype ActiveEdge = \"start\" | \"end\" | null\n\ninterface ScrollState {\n edge: ActiveEdge\n limit: number\n}\n\n/**\n * Per axis and scrollable element, for the current drag: the edge it's\n * auto-scrolling towards, \"start\" (top/left) or \"end\" (bottom/right), and\n * its scroll limit when that began (Bug 1 fix). A drag in a Reorder.Board\n * scrolls on both axes, often of one element (like the page), and can\n * scroll several, so they're kept per axis and all cleared when it ends.\n */\nconst scrollStates = {\n x: new Map<HTMLElement, ScrollState>(),\n y: new Map<HTMLElement, ScrollState>(),\n}\n\nexport function resetAutoScrollState(): void {\n scrollStates.x.clear()\n scrollStates.y.clear()\n}\n\nfunction isScrollableElement(element: Element, axis: \"x\" | \"y\"): boolean {\n const style = getComputedStyle(element)\n const overflow = axis === \"x\" ? style.overflowX : style.overflowY\n\n const isDocumentScroll =\n element === document.body ||\n element === document.documentElement\n\n /**\n * An element that scrolls on one axis computes overflow auto on both\n * (e.g. a column of a board that scrolls the other way), so it must also\n * have content to scroll on this one.\n */\n return (\n isDocumentScroll ||\n (overflowStyles.has(overflow) &&\n (axis === \"x\"\n ? element.scrollWidth > element.clientWidth\n : element.scrollHeight > element.clientHeight))\n )\n}\n\nfunction findScrollableAncestor(\n element: Element | null,\n axis: \"x\" | \"y\"\n): HTMLElement | null {\n let current = element?.parentElement\n while (current) {\n if (isScrollableElement(current, axis)) {\n return current\n }\n current = current.parentElement\n }\n return null\n}\n\n/**\n * Speed ramps up towards the edge and is capped at maxSpeed once the\n * pointer reaches or passes it. This runs from onDrag, within a frame, so\n * scale by the frame's elapsed time to scroll at the same speed whatever\n * the display's refresh rate.\n */\nfunction scrollSpeed(distanceFromEdge: number): number {\n const intensity = Math.min(1, 1 - distanceFromEdge / threshold)\n return maxSpeed * intensity * intensity * frameData.delta\n}\n\nfunction getScrollAmount(\n pointerPosition: number,\n scrollElement: HTMLElement,\n axis: \"x\" | \"y\"\n): { amount: number; edge: ActiveEdge } {\n const rect = scrollElement.getBoundingClientRect()\n\n const start = axis === \"x\" ? Math.max(0, rect.left) : Math.max(0, rect.top)\n const end = axis === \"x\" ? Math.min(window.innerWidth, rect.right) : Math.min(window.innerHeight, rect.bottom)\n\n const distanceFromStart = pointerPosition - start\n const distanceFromEnd = end - pointerPosition\n\n if (distanceFromStart < threshold) {\n return { amount: -scrollSpeed(distanceFromStart), edge: \"start\" }\n } else if (distanceFromEnd < threshold) {\n return { amount: scrollSpeed(distanceFromEnd), edge: \"end\" }\n }\n\n return { amount: 0, edge: null }\n}\n\nexport function autoScrollIfNeeded(\n groupElement: Element | null,\n pointerPosition: number,\n axis: \"x\" | \"y\",\n velocity: number\n): void {\n if (!groupElement) return\n\n const scrollableAncestor = findScrollableAncestor(groupElement, axis)\n if (!scrollableAncestor) return\n\n // Convert pointer position from page coordinates to viewport coordinates.\n // The gesture system uses pageX/pageY but getBoundingClientRect() returns\n // viewport-relative coordinates, so we need to account for page scroll.\n const viewportPointerPosition =\n pointerPosition - (axis === \"x\" ? window.scrollX : window.scrollY)\n\n const { amount: scrollAmount, edge } = getScrollAmount(\n viewportPointerPosition,\n scrollableAncestor,\n axis\n )\n\n const states = scrollStates[axis]\n\n // If not in any threshold zone, clear this axis' state\n if (edge === null) {\n states.delete(scrollableAncestor)\n return\n }\n\n let state = states.get(scrollableAncestor)\n\n const isDocumentScroll =\n scrollableAncestor === document.body ||\n scrollableAncestor === document.documentElement\n\n // If not currently scrolling this edge, check velocity to see if we should start\n if (state?.edge !== edge) {\n // Only start scrolling if velocity is towards the edge\n const shouldStart =\n (edge === \"start\" && velocity < 0) ||\n (edge === \"end\" && velocity > 0)\n if (!shouldStart) return\n\n // Activate this edge, and record the initial scroll limit\n // (prevents infinite scroll)\n state = {\n edge,\n limit:\n axis === \"x\"\n ? scrollableAncestor.scrollWidth - (isDocumentScroll ? window.innerWidth : scrollableAncestor.clientWidth)\n : scrollableAncestor.scrollHeight - (isDocumentScroll ? window.innerHeight : scrollableAncestor.clientHeight),\n }\n states.set(scrollableAncestor, state)\n }\n\n // Cap scrolling at initial limit (prevents infinite scroll)\n if (scrollAmount > 0) {\n const currentScroll =\n axis === \"x\"\n ? (isDocumentScroll ? window.scrollX : scrollableAncestor.scrollLeft)\n : (isDocumentScroll ? window.scrollY : scrollableAncestor.scrollTop)\n if (currentScroll >= state.limit) return\n }\n\n // Apply scroll\n if (axis === \"x\") {\n if (isDocumentScroll) {\n window.scrollBy({ left: scrollAmount })\n } else {\n scrollableAncestor.scrollLeft += scrollAmount\n }\n } else {\n if (isDocumentScroll) {\n window.scrollBy({ top: scrollAmount })\n } else {\n scrollableAncestor.scrollTop += scrollAmount\n }\n }\n}\n"],"names":[],"mappings":"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"}