Skip to content

Instantly share code, notes, and snippets.

@reecelucas
Last active July 18, 2024 15:01
Show Gist options
  • Save reecelucas/2f510e6b8504008deaaa52732202d2da to your computer and use it in GitHub Desktop.
Save reecelucas/2f510e6b8504008deaaa52732202d2da to your computer and use it in GitHub Desktop.
React hook to enable/disable page scroll
import { useRef } from 'react';
const safeDocument = typeof document !== 'undefined' ? document : {};
/**
* Usage:
* const [blockScroll, allowScroll] = useScrollBlock();
*/
export default () => {
const scrollBlocked = useRef();
const html = safeDocument.documentElement;
const { body } = safeDocument;
const blockScroll = () => {
if (!body || !body.style || scrollBlocked.current) return;
const scrollBarWidth = window.innerWidth - html.clientWidth;
const bodyPaddingRight =
parseInt(window.getComputedStyle(body).getPropertyValue("padding-right")) || 0;
/**
* 1. Fixes a bug in iOS and desktop Safari whereby setting
* `overflow: hidden` on the html/body does not prevent scrolling.
* 2. Fixes a bug in desktop Safari where `overflowY` does not prevent
* scroll if an `overflow-x` style is also applied to the body.
*/
html.style.position = 'relative'; /* [1] */
html.style.overflow = 'hidden'; /* [2] */
body.style.position = 'relative'; /* [1] */
body.style.overflow = 'hidden'; /* [2] */
body.style.paddingRight = `${bodyPaddingRight + scrollBarWidth}px`;
scrollBlocked.current = true;
};
const allowScroll = () => {
if (!body || !body.style || !scrollBlocked.current) return;
html.style.position = '';
html.style.overflow = '';
body.style.position = '';
body.style.overflow = '';
body.style.paddingRight = '';
scrollBlocked.current = false;
};
return [blockScroll, allowScroll];
};
@pedroalmeida415
Copy link

This is exatcly what I was looking for, and with such a simple solution, thank you very much man

@FooOperator
Copy link

this is truly incredible, I've already implemented in my project and gave you credit in code. will give you credit on readme when it's done!

@thiagosullivan
Copy link

I logged in all my accounts just to give a star in each of them. Thank you, is was very useful!

@Aycom366
Copy link

This doesn't work for me
The block scroll worked, but allowScroll doesn't work and am using nextjs

@lipezz
Copy link

lipezz commented Apr 7, 2023

Thank you so much

@Renz757
Copy link

Renz757 commented Apr 12, 2023

Thank you!! This works perfectly :)

@zwilderrr
Copy link

phenomenal !

@MowglyOrizon
Copy link

++ thanks!

@Hyporos
Copy link

Hyporos commented Jul 26, 2023

Thanks!

@silvertae
Copy link

Thanks a lot 👍

@chan4est
Copy link

chan4est commented Feb 7, 2024

Works! Thank you!!!!!!!!!

@litehacker
Copy link

improved hook for TypeScript:

import { useRef, useEffect } from 'react';

const safeDocument = typeof document !== 'undefined' ? document : {};

/**
 * Usage:
 * const [blockScroll, allowScroll] = useScrollBlock();
 */
export default function useScrollBlock(): [() => void, () => void] {
  const scrollBlocked = useRef<boolean>(false);
  const { body } = safeDocument as Document;
  const html = (safeDocument as Document).documentElement;

  const blockScroll = () => {
    if (!body || !body.style || scrollBlocked.current) return;

    const scrollBarWidth = window.innerWidth - (html?.clientWidth || 0);
    const bodyPaddingRight =
      parseInt(
        window.getComputedStyle(body).getPropertyValue('padding-right'),
      ) || 0;

    /**
     * 1. Fixes a bug in iOS and desktop Safari whereby setting
     *    `overflow: hidden` on the html/body does not prevent scrolling.
     * 2. Fixes a bug in desktop Safari where `overflowY` does not prevent
     *    scroll if an `overflow-x` style is also applied to the body.
     */
    if (html) {
      html.style.position = 'relative'; /* [1] */
      html.style.overflow = 'hidden'; /* [2] */
    }

    body.style.position = 'relative'; /* [1] */
    body.style.overflow = 'hidden'; /* [2] */
    body.style.paddingRight = `${bodyPaddingRight + scrollBarWidth}px`;

    scrollBlocked.current = true;
  };

  const allowScroll = () => {
    if (!body || !body.style || !scrollBlocked.current) return;

    if (html) {
      html.style.position = '';
      html.style.overflow = '';
    }

    body.style.position = '';
    body.style.overflow = '';
    body.style.paddingRight = '';

    scrollBlocked.current = false;
  };

  useEffect(() => {
    return () => {
      if (scrollBlocked.current) {
        allowScroll();
      }
    };
  }, []);

  return [blockScroll, allowScroll];
}

@NicolasHov
Copy link

Thanks Lucas :)

@ciruz
Copy link

ciruz commented May 15, 2024

thanks @litehacker for the TS version

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