useScrollLock
A hook that allows you to disable scrolling on an HTML element.
Source Code
import { RefObject, useEffect, useRef } from "react";
export type ScrollLockTarget =
| string
| HTMLElement
| RefObject<HTMLElement>
| undefined;
const getTargetElement = (target: ScrollLockTarget): HTMLElement | null => {
if (!target) return document.body;
if (target instanceof HTMLElement) return target;
if (typeof target === "string") return document.querySelector(target);
if (target && "current" in target) return target.current || null;
return null;
};
interface StyleBackup {
overflow: string;
scrollbarGutter: string;
}
const styleBackupMap = new WeakMap<HTMLElement, StyleBackup>();
const lockCounterMap = new WeakMap<HTMLElement, number>();
export const useScrollLock = (isLocked: boolean, target?: ScrollLockTarget) => {
const targetElementRef = useRef<HTMLElement | null>(null);
useEffect(() => {
targetElementRef.current = getTargetElement(target);
}, [target]);
useEffect(() => {
const targetElement = targetElementRef.current;
if (!targetElement || !isLocked) return;
const currentCount = lockCounterMap.get(targetElement) || 0;
// Backup styles before first lock
if (currentCount === 0) {
styleBackupMap.set(targetElement, {
overflow: targetElement.style.overflow,
scrollbarGutter: targetElement.style.scrollbarGutter,
});
}
const newCount = currentCount + 1;
lockCounterMap.set(targetElement, newCount);
Object.assign(targetElement.style, {
overflow: "hidden",
scrollbarGutter: "stable",
});
return () => {
const currentCount = lockCounterMap.get(targetElement) || 0;
const newCount = Math.max(0, currentCount - 1);
lockCounterMap.set(targetElement, newCount);
if (newCount === 0) {
const previousStyles = styleBackupMap.get(targetElement) || {
overflow: "",
scrollbarGutter: "",
};
Object.assign(targetElement.style, previousStyles);
styleBackupMap.delete(targetElement);
}
};
}, [isLocked]);
};
Features
- No Layout Shift - Forces
scrollbar-gutter
tostable
to prevent layout shifts when the scroll is locked. - No Side Effects - The hook only adds styles to the target element and removes them (reverting to the previous styles) when the hook is unmounted.
- Custom Target - Allows you to lock the scroll on a specific element or the entire page.
API Reference
Prop | Default | Type | Description |
---|---|---|---|
| - |
| Whether to lock the scroll. |
| - |
| The target element to lock the scroll on. Can be a string (selector), HTMLElement, or a RefObject with a HTMLElement. |