UNPKG

2.44 kBJavaScriptView Raw
1const getScrollingRoot = () => document.scrollingElement || document.documentElement;
2
3/**
4 * Recursively finds the scroll parent of a node. The scroll parrent of a node
5 * is the closest node that is scrollable. A node is scrollable if:
6 * - it is allowed to scroll via CSS ('overflow-y' not visible or hidden);
7 * - and its children/content are "bigger" than the node's box height.
8 *
9 * The root of the document always scrolls by default.
10 *
11 * @param {HTMLElement} node Any DOM element.
12 * @return {HTMLElement} The scroll parent element.
13 */
14export function getScrollParent(node) {
15 const parent = node.parentElement;
16
17 if (parent == null) return getScrollingRoot();
18
19 const { overflowY } = window.getComputedStyle(parent);
20 const canScroll = overflowY !== 'visible' && overflowY !== 'hidden';
21
22 if (canScroll && parent.scrollHeight > parent.clientHeight) {
23 return parent;
24 }
25
26 return getScrollParent(parent);
27}
28
29/**
30 * Recursively traverses the tree upwards from the given node, capturing all
31 * ancestor nodes that scroll along with their current 'overflow-y' CSS
32 * property.
33 *
34 * @param {HTMLElement} node Any DOM element.
35 * @param {Map<HTMLElement,string>} [acc] Accumulator map.
36 * @return {Map<HTMLElement,string>} Map of ancestors with their 'overflow-y' value.
37 */
38export function getScrollAncestorsOverflowY(node, acc = new Map()) {
39 const scrollingRoot = getScrollingRoot();
40 const scrollParent = getScrollParent(node);
41 acc.set(scrollParent, scrollParent.style.overflowY);
42
43 if (scrollParent === scrollingRoot) return acc;
44 return getScrollAncestorsOverflowY(scrollParent, acc);
45}
46
47/**
48 * Disabling the scroll on a node involves finding all the scrollable ancestors
49 * and set their 'overflow-y' CSS property to 'hidden'. When all ancestors have
50 * 'overflow-y: hidden' (up to the document element) there is no scroll
51 * container, thus all the scroll outside of the node is disabled. In order to
52 * enable scroll again, we store the previous value of the 'overflow-y' for
53 * every ancestor in a closure and reset it back.
54 *
55 * @param {HTMLElement} node Any DOM element.
56 */
57export default function disableScroll(node) {
58 const scrollAncestorsOverflowY = getScrollAncestorsOverflowY(node);
59 const toggle = (on) => scrollAncestorsOverflowY.forEach((overflowY, ancestor) => {
60 ancestor.style.setProperty('overflow-y', on ? 'hidden' : overflowY);
61 });
62
63 toggle(true);
64 return () => toggle(false);
65}