Created
November 19, 2025 14:10
-
-
Save simonsmith/34c1201ad771eb5c2b6a2718fad56385 to your computer and use it in GitHub Desktop.
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
| 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