Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't scroll parent component when Modal is opened #191

Open
rahulthewall opened this issue Jun 24, 2016 · 51 comments
Open

Don't scroll parent component when Modal is opened #191

rahulthewall opened this issue Jun 24, 2016 · 51 comments

Comments

@rahulthewall
Copy link

rahulthewall commented Jun 24, 2016

Summary: Don't scroll parent component when Modal is opened

Steps to reproduce:

  1. Implement a Modal
  2. Open the Modal
  3. The parent component of the Modal is still scrollable while the Modal is open

Expected behavior: The Component that calls the Modal should not be scrollable when the Modal is opened.

Is there any way I can change this?

@diasbruno
Copy link
Collaborator

diasbruno commented Jul 1, 2016

@rahulthewall there is an callback called onAfterOpen. you can install a callback to disable the desired element.

@emmerich
Copy link

emmerich commented Jul 5, 2016

@rahulthewall Or in CSS, something like:

.ReactModal__Body--open {
  overflow-y: hidden;
}

Which will prevent overflow-y on the body whilst the modal is open.

@amangeot
Copy link

amangeot commented Jul 30, 2016

Hello @diasbruno @emmerich ,are your solutions work arounds or best / common practices ?

Is the CSS going to work in all browsers ? I don't really know this syntax, but it's working with sass ?

@diasbruno
Copy link
Collaborator

@amangeot sometimes you need to change the behaviour of your application because of an event X. so it's an ok solution.

@emmerich's css class, .ReactModal__Body--open, is defined by the react-modal, but it has no effect because this rule is not defined by react-modal. It's not defined because if you want the oppose behaviour you would need to be more hacky overriding the .ReactModal__Body--open rule to let the page scroll.

you can use afterOpen which is the programmatically way and you can attach a css class to any element.

@diasbruno
Copy link
Collaborator

...if it's a cross-browser solution, you always need to test. A good place to check which browsers implements this feature is Mozilla's MDN CSS
MDN overflow-y

@emmerich
Copy link

@amangeot overflow-y works with all browsers and I assume React Modal works with the same browser set as React.

@diasbruno I didn't understand this: .ReactModal__Body--open, is defined by the react-modal, but it has no effect because this rule is not defined by react-modal.. Could you clarify? Not sure what the problem is with using a class defined by React Modal for React Modal-specific behaviour.

@diasbruno
Copy link
Collaborator

gee, that was a very bad explanation...sorry.

.ReactModal__Body--open class is added to the body element by react-modal, but the class itself is not defined.

@carlows
Copy link

carlows commented Aug 27, 2016

Is there another workaround for this? I would like the parent component not to be scrollable while preserving the page scroll

@carlows
Copy link

carlows commented Aug 27, 2016

nevermind, just need to set position: absolute for the element instead of position: fixed

@diasbruno
Copy link
Collaborator

I'm closing this issue for clean up. Thank you all for the tricks, hope we can publish this on the docs.

@AlecRust
Copy link

For this to work on iOS position, width and height are also necessary:

.ReactModal__Body--open {
  overflow: hidden;
  position: fixed;
  width: 100%;
  height: 100%;
}

However this will cause the page to jump to the top on modal open.

@kaiomagalhaes
Copy link

I'm facing exactly this same issue but only for IOS. The overflow hidden on the body works just fine for the others platforms though. Does anyone found a good solution that doesn't have backfires like the position going to the top after the modal being opened?

@AlecRust
Copy link

AlecRust commented Aug 6, 2017

@kaiomagalhaes only to use react-aria-modal instead.

@wiggitamoo
Copy link

I am also looking for a solution to the position of the parent page scrolling to the top when the modal is opened.

@kaiomagalhaes
Copy link

@wiggitamoo I've gone with @AlecRust solution, the other react modal does't have this issue.

@watch-janick
Copy link

Thanks @AlecRust, I'll use this modal as well.

@kevinzhangftw
Copy link

kevinzhangftw commented May 31, 2018

hey tried
parent { overflow: hidden; position: fixed; width: 100%; height: 100%; }

Unfortunately it also stops the parent from scrolling completely. @diasbruno This is ios only issue. onAfterOpen will still scroll the background if i do it like this:

<Modal isOpen={open} onRequestClose={this.handleCloseModal} style={styles.modal} contentLabel="Example Modal" onAfterOpen={this.disableScroll} >

and then my disableScroll is defined like this

disableScroll = () => { document.body.style.overflow = 'hidden' }

@mbrowne
Copy link

mbrowne commented Jun 4, 2018

@ wiggitamoo

I am also looking for a solution to the position of the parent page scrolling to the top when the modal is opened.

This thread is quite old, but in case it's helpful to others, I found that the way to prevent this issue is to set top, right, bottom, and left to zero:

overflow: hidden; position: fixed; top: 0; right: 0; bottom: 0; left: 0;

@kaiomagalhaes
Copy link

@mbrowne does it solve for when you need to scroll the content of the modal?

@mbrowne
Copy link

mbrowne commented Jun 5, 2018

As long as the container holding your modal content has overflow: auto or overflow-y: auto then yes.

@triathletefr23-zz
Copy link

Hello. Does anyone know the solution to this issue? I tried all proposed ones and have again the scroll of the background enabled.

@kaiomagalhaes
Copy link

@triathlet23 I've gone with @AlecRust solution, the other react modal doesn't have this issue.

@triathletefr23-zz
Copy link

Thanks for a suggestion, but we've built a complete app with the react modal. If smb is interested, I used the solution that was partly proposed in the thread :
OnAfterClose={() => document.body.style.overflow = 'hidden' }
OnRequestClose={() => document.body.removeAttribute('style') }
It woks fine for me and hasn't any side effects.

@diasbruno diasbruno reopened this Jul 17, 2018
@diasbruno
Copy link
Collaborator

I'll reopen this issue, so it's visible for people looking for help.

@WemkoDijkhuis
Copy link
Contributor

@kaiomagalhaes only to use react-aria-modal instead.

react-aria-modal from davidtheclark uses https://github.com/davidtheclark/no-scroll. So you can also implement that into your modal to fix the scroll issue. It basically disables the document scroll behavoir.

@ghost
Copy link

ghost commented Mar 26, 2019

no-scroll helped us to solve this issue. :)

We did something like this:

  useEffect(() => {
    if (open) {
      noScroll.on()
    }

    return () => noScroll.off()
  })

@bertho-zero
Copy link

shouldFocusAfterRender={false}

works fine for me.

@Bartlebyy
Copy link

no-scroll explicitly says that it does not work for iOS in their readme
image
and references to use the solution at body-scroll-lock, which explains the cons to many of the approaches suggested above in their readme
image
After trying many of the above suggested solutions, and getting the errors that are stated in the body-scroll-lock readme. I have decided to go with the aforementioned package that solves this clearly-not-one-simple-solution-fits-all problem.

@ghost
Copy link

ghost commented Apr 9, 2019

no-scroll explicitly says that it does not work for iOS in their readme

I've read this too and no-scroll is working for us on iOS.

@YassienW
Copy link
Contributor

YassienW commented Jul 7, 2019

@diasbruno Is there a reason as to why this behavior is not supported internally by react-modal? It seems to me that in most cases you would want to disable scroll when you open a modal, perhaps there should at least be a prop to enable/disable this behaviour.

@nandorojo
Copy link

Thanks for a suggestion, but we've built a complete app with the react modal. If smb is interested, I used the solution that was partly proposed in the thread :
OnAfterClose={() => document.body.style.overflow = 'hidden' }
OnRequestClose={() => document.body.removeAttribute('style') }
It woks fine for me and hasn't any side effects.

This worked for me except I think there's a typo: it should be onAfterOpen, not onAfterClose. Does this solution work for you on mobile too?

@diasbruno
Copy link
Collaborator

@YassienW Sorry for the delay on this. Yeah, totally agree. Unfortunately, I don't have much time to work on this. So, if anyone has interest, and time, to help this, it would be great.

@comountainclimber
Copy link

comountainclimber commented Oct 21, 2019

#191 (comment)

Using https://www.npmjs.com/package/no-scroll was the only solution that worked for me... All others had the side effect of "bumping the scroll position to the top of the page" when the modal was rendered

@oskvortsov
Copy link

oskvortsov commented Jan 16, 2020

This example of how you can lock body scroll for mobile and desktop
Tested on Safari (iphone 5s, iphone X) and three android devices
scroll lock react-modal

@zhenyanghua
Copy link

Change the overflow to hidden might change the width of the page when the scroll bar is hidden, this causes visual disruption in user visual.

@mbrowne
Copy link

mbrowne commented Apr 5, 2020

There are several alternatives to using overflow:hidden if you find that visual disruption is happening...
https://stackoverflow.com/a/38539493/560114

@vinzloh
Copy link

vinzloh commented Apr 25, 2020

I'm facing exactly this same issue but only for IOS. The overflow hidden on the body works just fine for the others platforms though. Does anyone found a good solution that doesn't have backfires like the position going to the top after the modal being opened?

If like me, you spent hours trying to find a scroll solution for Safari 10, I found it and made a simple React hooks version here: https://codesandbox.io/s/react-modal-scrollable-safari-10-friendly-3xzzy

@anacarolinahernandes
Copy link

Thanks for a suggestion, but we've built a complete app with the react modal. If smb is interested, I used the solution that was partly proposed in the thread :
OnAfterClose={() => document.body.style.overflow = 'hidden' }
OnRequestClose={() => document.body.removeAttribute('style') }
It woks fine for me and hasn't any side effects.

I used a similar solution:

onAfterOpen={() => { document.body.style.overflow = 'hidden'; }} onAfterClose={() => { document.body.removeAttribute('style'); }}

I left onRequestClose specifically to close the modal or any other action that the page needs.

@mmirus
Copy link

mmirus commented Jun 4, 2020

In case it helps anyone, using body-scroll-lock could look something like this:

import React, { useEffect } from "react";
import ReactModal from "react-modal";
import {
  disableBodyScroll,
  enableBodyScroll,
  clearAllBodyScrollLocks,
} from "body-scroll-lock";

export const Modal = ({
  isOpen,
  children,
  onRequestClose,
  ...props
}) => {
  ReactModal.setAppElement("#my-app-element");

  let contentEl = null;
  useEffect(() => {
    return clearAllBodyScrollLocks;
  });

  return (
    <ReactModal
      isOpen={isOpen}
      onAfterOpen={() => disableBodyScroll(contentEl)}
      onRequestClose={() => {
        enableBodyScroll(contentEl);
        onRequestClose();
      }}
      contentRef={(element) => (contentEl = element)}
      {...props}
    >
        {children}
    </ReactModal>
  );
};

Depending on your use case, you could use overlayRef instead of contentRef and update the rest accordingly.

The onRequestClose prop passed into this wrapper component can just be a function passed from the parent that updates the parent's state. Pseudo-code:

const ModalParent = () => {
  const [modalOpen, setModalOpen] = useState(false);

  return (
    <div>
      <button onClick={() => setModalOpen(true)}>open modal</button>

      <Modal isOpen={modalOpen} onRequestClose={() => setModalOpen(false)}>
        Modal content
      </Modal>
    </div>
  );
};

@mosesoak
Copy link

To the authors of React Modal: I'd assumed that locking the body scroll was a built-in feature and that was one of the main reasons for using this lib. Is this just a regression or was that never actually included in functionality? Pretty much a must-have in all situations. Thanks!

@irwansyafani
Copy link

I just read the docs and put code htmlOpenClassName as attribute
Hopefully it would works for you guys ✨🚀

result:

<Modal
  // other attributes
  htmlOpenClassName="overflow-hidden"
>
// your html
</Modal>

Btw, I used Tailwind for that and you can change the class value based on your CSS Frameworks

@maxgfr
Copy link

maxgfr commented Sep 6, 2021

Hi, I've also solved this issue by using this hooks : https://gist.github.com/reecelucas/2f510e6b8504008deaaa52732202d2da

import Modal from 'react-modal';
....
  const [blockScroll, allowScroll] = useScrollBlock();
const ModalParent = () => {
  const [modalOpen, setModalOpen] = useState(false);
....
  return (
    <div>
      <button onClick={() => {setModalOpen(true); blockScroll(); }>open modal</button>

      <Modal isOpen={modalOpen} onRequestClose={() => {setModalOpen(false); allowScroll();}}>
        Modal content
      </Modal>
    </div>
  );
};

@diasbruno
Copy link
Collaborator

diasbruno commented Sep 6, 2021

@maxgfr This is a nice answer. It would be nice if it was in the docs... 😉

@timmeade
Copy link

timmeade commented Jan 4, 2022

So if you are using react and have an open state hook this appears to work. I'm still testing though.

  useEffect(() => {
    if (open) document.body.style.overflow = 'hidden';
    else document.body.removeAttribute('style');
    return () => {
      document.body.removeAttribute('style');
    };
  }, [open]);

@diasbruno
Copy link
Collaborator

This issues is terrible, mostly because it doesn't have a good answer. In 6 years, there are a lot of browser versions out there, some of them with this unfortunate behavior (or bug).

Best scenario is to add a library to handle this, but I'm not sure if it would cause too much trouble on existing projects.

A nice implementation is to provide a simple component that can extend react-modal.

const Modal = (props) => {
  return (
    <ReactModal {...props} 
      onAfterOpen={() => libToBlockScroll()} 
      onRequestClose={() => (libToUnblockScroll(), props.onRequestClose())}
     >{props.children}</ReactModal>
  );
}

If anyone want to help with this, with some ideas, I'd appreciate.

@CharlotteArai
Copy link

I've added a z-index to the overlay and it prevents the parent from scrolling. Maybe this isn't the solution you were looking for but it works for me in all browsers I've tested so far (I don't know if it works on iOS)

@dckesler
Copy link

dckesler commented Jun 14, 2022

For those of you who want a solution that doesn't cause the scrollbar to disappear, we are using this solution in our library.

export function useFixedBody(isOpen: boolean) {
  useEffect(() => {
    if (isOpen) {
      const x = window.scrollX;
      const y = window.scrollY;
      const disableScroll = () => {
        window.scrollTo(x, y);
      };
      window.addEventListener('scroll', disableScroll);
      return () => {
        window.removeEventListener('scroll', disableScroll);
      };
    }
  }, [isOpen]);
}

@YassienW
Copy link
Contributor

YassienW commented Jun 15, 2022

For those of you who want a solution that doesn't cause the scrollbar to disappear, we are using this solution in our library.

export function useFixedBody(isOpen: boolean) {
  useEffect(() => {
    if (isOpen) {
      const x = window.scrollX;
      const y = window.scrollY;
      const disableScroll = () => {
        window.scrollTo(x, y);
      };
      window.addEventListener('scroll', disableScroll);
      return () => {
        window.removeEventListener('scroll', disableScroll);
      };
    }
  }, [isOpen]);
}

This seems like the simplest most straight forward solution. I just wonder if this potentially causes cross compatibility issues

@diasbruno
Copy link
Collaborator

Although this solution is simple, there are libraries out there that can handle this. It should be preferred because they may have fixes some quirks related to browsers and their versions.

@bohdan-kravchuk
Copy link

For those of you who want a solution that doesn't cause the scrollbar to disappear, we are using this solution in our library.

export function useFixedBody(isOpen: boolean) {
  useEffect(() => {
    if (isOpen) {
      const x = window.scrollX;
      const y = window.scrollY;
      const disableScroll = () => {
        window.scrollTo(x, y);
      };
      window.addEventListener('scroll', disableScroll);
      return () => {
        window.removeEventListener('scroll', disableScroll);
      };
    }
  }, [isOpen]);
}

If the content in the modal is too long there are two scrollbars. So this fix is also not perfect.
You may say that modal height should be limited. But it's not possible when you want to have a form that has a multi-select field with no limited amount of values.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests