Skip to content

Instantly share code, notes, and snippets.

@simonsmith
Created November 19, 2025 14:10
Show Gist options
  • Select an option

  • Save simonsmith/34c1201ad771eb5c2b6a2718fad56385 to your computer and use it in GitHub Desktop.

Select an option

Save simonsmith/34c1201ad771eb5c2b6a2718fad56385 to your computer and use it in GitHub Desktop.
import {useEffect, useRef, RefObject} from 'react';
import {detectDevice} from '../../Atoms';
const styles = {
'touch-action': 'none',
overflow: 'hidden',
'overscroll-behavior': 'none',
};
const iosStyles = {
// Necessary to reset the scroll position on iOS Safari with position fixed
// https://markus.oberlehner.net/blog/simple-solution-to-prevent-body-scrolling-on-ios
position: 'fixed',
left: '0',
right: '0',
};
/**
* Stops the document.body from scrolling
* https://benfrain.com/preventing-body-scroll-for-modals-in-ios/
* Also tested on Android Chrome
*/
export const useDocumentBodyScrollLock = ({
ref,
}: {
ref: RefObject<HTMLDivElement | null>;
}) => {
const scrollPosition = useRef(0);
useEffect(() => {
const {os} = detectDevice(navigator.userAgent);
scrollPosition.current = window.pageYOffset;
Object.entries(styles).forEach(([cssProp, cssValue]) => {
document.body.style.setProperty(cssProp, cssValue, 'important');
});
// The issue is only present on iOS
if (os === 'ios') {
Object.entries(iosStyles).forEach(([cssProp, cssValue]) => {
document.body.style.setProperty(cssProp, cssValue, 'important');
});
document.body.style.setProperty('top', `-${scrollPosition.current}px`);
// By the time the effect runs the ref will be defined
const isFocusedElementInsideModal = ref.current?.contains(
document.activeElement
);
// On mobile device if the element is focused initially (with `autoFocus`) then
// the keyboard opening will scroll the modal into a weird position
// This resets the scroll position to the top of the modal if the
// activeElement is inside
// Small delay is needed to ensure this runs after the keyboard has opened
if (isFocusedElementInsideModal) {
setTimeout(() => {
window.scrollTo(0, 0);
}, 50);
}
}
return () => {
Object.keys(styles).forEach((cssProp) => {
document.body.style.removeProperty(cssProp);
});
if (os === 'ios') {
Object.keys(iosStyles).forEach((cssProp) => {
document.body.style.removeProperty(cssProp);
});
document.body.style.removeProperty('top');
window.scrollTo(0, scrollPosition.current);
}
};
}, [ref]);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment