Last active
May 8, 2024 09:33
-
-
Save souporserious/f8783de960108c5700a4f60590a0e9c3 to your computer and use it in GitHub Desktop.
lock scrollbars
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Lock all scrollbars by disabling mousewheel and locking scrollbars in position | |
* Optionally provide an element to allow it to scroll when hovered | |
*/ | |
const listenerOptions = supportsPassiveEvents | |
? { capture: true, passive: false } | |
: true | |
let lockedScrolls = [] | |
function lockScroll(node) { | |
const scrollCoords = new WeakMap(); | |
const preventDefault = (event) => event.preventDefault(); | |
const preventScrollKeys = (event) => { | |
const scrollKeys = [ | |
'Space', 'PageUp', 'PageDown', 'End', 'Home', | |
'ArrowLeft', 'ArrowUp', 'ArrowRight', 'ArrowDown' | |
]; | |
if (scrollKeys.includes(event.key)) { | |
event.preventDefault(); | |
} | |
}; | |
const mouseOver = () => { | |
window.removeEventListener('wheel', preventDefault, listenerOptions); | |
window.removeEventListener('keydown', preventScrollKeys, listenerOptions); | |
}; | |
const mouseOut = () => { | |
window.addEventListener('wheel', preventDefault, listenerOptions); | |
window.addEventListener('keydown', preventScrollKeys, listenerOptions); | |
}; | |
const mouseDown = (event) => { | |
if (event.target !== node) { | |
scrollCoords.set(event.target, { | |
x: event.target.scrollLeft, | |
y: event.target.scrollTop, | |
}); | |
} | |
}; | |
const wheelLock = (event) => { | |
const scrollableDistance = node.scrollHeight - node.offsetHeight; | |
if ( | |
(event.deltaY > 0 && node.scrollTop >= scrollableDistance) || | |
(event.deltaY < 0 && node.scrollTop <= 0) | |
) { | |
event.preventDefault(); | |
} | |
}; | |
const scrollLock = (event) => { | |
const scrollElement = event.target === document ? document.documentElement : event.target; | |
const scrollElementCoords = scrollCoords.get(scrollElement); | |
if (scrollElementCoords) { | |
scrollElement.scrollLeft = scrollElementCoords.x; | |
scrollElement.scrollTop = scrollElementCoords.y; | |
} | |
}; | |
const addListeners = () => { | |
if (node) { | |
node.addEventListener('mouseover', mouseOver, true); | |
node.addEventListener('mouseout', mouseOut, true); | |
mouseOut(); | |
} | |
window.addEventListener('mousedown', mouseDown, true); | |
window.addEventListener('wheel', wheelLock, listenerOptions); | |
window.addEventListener('scroll', scrollLock, true); | |
window.addEventListener('keydown', preventScrollKeys, listenerOptions); | |
}; | |
const removeListeners = () => { | |
if (node) { | |
node.removeEventListener('mouseover', mouseOver, true); | |
node.removeEventListener('mouseout', mouseOut, true); | |
mouseOver(); | |
} | |
window.removeEventListener('mousedown', mouseDown, true); | |
window.removeEventListener('wheel', wheelLock, listenerOptions); | |
window.removeEventListener('scroll', scrollLock, true); | |
window.removeEventListener('keydown', preventScrollKeys, listenerOptions); | |
}; | |
if (lockedScrolls.length > 0) { | |
// check if we need to remove any current scroll locks | |
lockedScrolls[lockedScrolls.length - 1].removeListeners() | |
// filter any nodes that may be gone from the DOM now | |
lockedScrolls = lockedScrolls.filter((lockedScroll) => | |
document.body.contains(lockedScroll.node) | |
) | |
} | |
// store the scroll lock enable/disable methods so we can disable them when | |
// new ones are created or enable them when old ones are destroyed | |
if (document.body.contains(node)) { | |
lockedScrolls.push({ | |
node, | |
addListeners, | |
removeListeners, | |
}) | |
addListeners() | |
} | |
return () => { | |
const index = lockedScrolls.findIndex((scroll) => scroll.node === node) | |
if (index > -1) { | |
removeListeners() | |
// remove this scroll lock from stored scroll locks | |
lockedScrolls.splice(index, 1) | |
// again, filter any nodes that may be gone from the DOM now | |
lockedScrolls = lockedScrolls.filter((lockedScroll) => { | |
const containsLockedScrollNode = document.body.contains( | |
lockedScroll.node | |
) | |
// as a side effect, make sure to remove listeners from this node | |
if (containsLockedScrollNode === false) { | |
lockedScroll.removeListeners() | |
} | |
return containsLockedScrollNode | |
}) | |
// enable the previous scroll lock if any are left | |
if (lockedScrolls.length > 0) { | |
lockedScrolls[lockedScrolls.length - 1].addListeners() | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment