Skip to content

Instantly share code, notes, and snippets.

@reecelucas
Last active November 13, 2024 08:19
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];
};
@CSumm
Copy link

CSumm commented Jun 26, 2021

Awesome! This is exactly what I was looking for. Thanks!

@davidguillemet
Copy link

Thanks for that.
Personally, I have to set overflow as 'auto' to make allowScroll working in:

html.style.overflow = "auto'";
body.style.overflow = "auto";

(Working on Mac + vscode + Chrome)

@danon
Copy link

danon commented Sep 13, 2021

When I call disableBlock(), my absolutely positioned sidebar is moved up for some reason.
@reecelucas any ideas?

PS: Fixed. I was using position:sticky. For some reason @reecelucas doesn't work with sticky. After I changed it to position:fixed it works fine.

@0sama
Copy link

0sama commented Sep 13, 2021

Amazing hook thank you 👍👍
one small suggestion, try replacing parseInt with Number to avoid the radix parameter issue.
Number(window.getComputedStyle(body).getPropertyValue('padding-right')) || 0;

@kausett
Copy link

kausett commented Dec 3, 2021

Awesome Hook & Appreciate 🙏🏿 . Added hook to my tool kit!!

@Gabrielk99
Copy link

Is very useful, thanks a lot.

@barrylachapelle
Copy link

+1 well done

@gpspelle
Copy link

gpspelle commented Jan 3, 2022

Hi... I had to explore it differently for my specific scenario... I hope that it can be helpful to someone.

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] */
    html.style.height = "100%"
    body.style.position = "relative" /* [1] */
    body.style.overflow = "hidden" /* [2] */
    body.style.height = "100%"
    body.style.paddingRight = `${bodyPaddingRight + scrollBarWidth}px`

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

    html.style.position = ""
    html.style.overflow = ""
    html.style.height = ""
    body.style.position = ""
    body.style.overflow = ""
    body.style.paddingRight = ""
    body.style.maxHeight = ""

    scrollBlocked.current = false
  }

I had to add this height property to body and html, otherwise, I was seeing some bottom screen black background.

@Mooenz
Copy link

Mooenz commented Feb 26, 2022

Thanks :)

@luizlopes12
Copy link

Thanks, was very useful to me!

@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