Skip to content

Instantly share code, notes, and snippets.

@gragland
Last active October 11, 2019 17:49
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gragland/f50690d2724aec1bd513de8596dcd9b9 to your computer and use it in GitHub Desktop.
Save gragland/f50690d2724aec1bd513de8596dcd9b9 to your computer and use it in GitHub Desktop.
import { useState, useLayoutEffect } from 'react';
// Usage
function App(){
// State for our modal
const [modalOpen, setModalOpen] = useState(false);
return (
<div>
<button onClick={() => setModalOpen(true)}>Show Modal</button>
<Content />
{modalOpen && (
<Modal
title="Try scrolling"
content="I bet you you can't! Muahahaha 😈"
onClose={() => setModalOpen(false)}
/>
)}
</div>
);
}
function Modal({ title, content, onClose }){
// Call hook to lock body scroll
useLockBodyScroll();
return (
<div className="modal-overlay" onClick={onClose}>
<div className="modal">
<h2>{title}</h2>
<p>{content}</p>
</div>
</div>
);
}
// Hook
function useLockBodyScroll() {
useLayoutEffect(() => {
// Get original value of body overflow
const originalStyle = window.getComputedStyle(document.body).overflow;
// Prevent scrolling on mount
document.body.style.overflow = 'hidden';
// Re-enable scrolling when component unmounts
return () => document.body.style.overflow = originalStyle;
}, []); // Empty array ensures effect is only run on mount and unmount
}
@brunodesde1987
Copy link

brunodesde1987 commented Feb 18, 2019

EDIT: First of all, thanks for this. 👍

Saddly doesn't work on mobile 😞.

I tried on my Moto G5s + Chrome and on iPhone 8 Plus + Chrome too 🤔:
uselockbodyscroll

@gragland
Copy link
Author

@solutweb Ah good point. When I have some time will have to dig into what's going on there. Open to pull requests if you happen to figure it out!

@heyflynn
Copy link

in addition to width issue @anyexinglu pointed out about windows scrollbars, there is also an issue with with fixed backgrounds resizing when you overflow:hidden the scrollbars. material-ui tries to resolve this by adding padding to any element with the class mui-fixed.

@csenio
Copy link

csenio commented Aug 20, 2019

Why does it use useLayoutEffect? Seems unnecessary to me since we only use it on mount and unmount.

@lcvbeek
Copy link

lcvbeek commented Sep 25, 2019

When using it in TypeScript, it complains that the first param doesn't match EffectCallback, since it's returning () => string.

Changing return () => (document.body.style.overflow = 'visible') into return () => {document.body.style.overflow = 'visible'} fixes this.

@JohnBRTS
Copy link

JohnBRTS commented Oct 8, 2019

This currently won't work for iOS devices. You could rewrite it like this

  // Create ref to bind to element that should prevent scrolling
  const ref = useRef(null);
  useEffect(() => {
    // Add/Remove listeners for touchmove -> finding touchmove better, as it doesn't prevent other touch events like clicking links
    // The checks for ref && ref.current made it more TS friendly for me
    if (ref && ref.current) {
      ref.current.addEventListener('touchmove', e => e.preventDefault());
    }
    return () => {
      if (ref && ref.current) {
        ref.current.removeEventListener('touchmove', e => e.preventDefault());
      }
    };
  }, []);
  useLayoutEffect(() => {
    // Get original body overflow
    const originalStyle = window.getComputedStyle(document.body).overflow;
    // Prevent scrolling on mount
    document.body.style.overflow = 'hidden';
    // Re-enable scrolling when component unmounts
    return () => {
      return (document.body.style.overflow = originalStyle);
    };
  }, []); // Empty array ensures effect is only run on mount and unmount
  // Return the ref, to bind it to the element;
  return ref;
}````

And by binding the ref to the element that locks the scrolling, iOS devices will also respect the scroll lock

@dallanlee
Copy link

dallanlee commented Oct 11, 2019

I took a shot at modifying this to make it work:

  1. On iOS
  2. When multiple "instances" of lockBodyScroll (modals in this case) are active

It seems to be working with my changes in my sandbox fork here: https://codesandbox.io/s/uselockbodyscroll-example-mq9rm.
Or test it on your iOS device here: https://mq9rm.csb.app/

But I'm pretty new to custom hooks (and programming, really) so I'm open to suggestions/critique 👍🏼

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment