React-modal: background element under overlay is draggable when modal is opened in ios safari

Created on 12 Apr 2017  路  9Comments  路  Source: reactjs/react-modal

Summary:

in ios safari, you can drag the background elements under overlay after open modal.
also, if you have a background image on body, even used position: fixed or overflow: hidden, you still can drag the image

Steps to reproduce:

  1. open modal in ios safari
  2. drag around the overlay
  3. background divs is moving and scrolling

Expected behavior:

should work as desktop and stop scrolling

needs info

Most helpful comment

I've tested a lightweight workaround, while it's not inside react-modal package, may be it could help someone.

  const preventIOSScroll = (e) => {
    e.preventDefault();
  }

  <Modal
      isOpen={showModal}
      contentLabel="Modal"
      onAfterOpen={() => {
        document.documentElement.style.overflowY = 'hidden';
        document.documentElement.addEventListener('touchmove', preventIOSScroll);
      }}
      onRequestClose={() => {
        document.documentElement.style.overflowY = 'visible';
        document.documentElement.removeEventListener('touchmove', preventIOSScroll);
      }}
      shouldCloseOnOverlayClick
      style={({ overlay: { zIndex: 100, backgroundColor: 'rgba(0, 0, 0, 0.4)' } })}
  >
    <YourComponentsInsideModal />
  </Modal>

I like to move this logic inside static methods, but you could place it as described before. Tested with almost all accessible iOS devices within SimulatorApp.

All 9 comments

@diasbruno I also encountered this problem.

My configuration:

<ReactModal
  isOpen={isOpen}
  contentLabel="Modal"
  style={{
    overlay: {
      zIndex: 10,
      backgroundColor: 'rgba(0, 0, 0, 0.5)',
    },
    content: {
      padding: '0',
      borderRadius: '8px',
      border: 'none',
      top: '20px',
      left: '20px',
      right: '20px',
      bottom: 'initial',
      overflow: 'auto',
      WebkitOverflowScrolling: 'touch',
    },
  }}
>

I, too, am encountering this problem.

@chuanxie @Aaaaaashu @musicalshore Did you find a css trick to prevent this from happening?

Yes, add position: fixed to the body element when the modal is open

I'd like to point out that this is a known issue with Webkit itself, and not something this library has a lot of control over. This also means it will affect any iOS browser using the Webkit engine, including Chrome, Safari, and others.

Strangely (at least for me), the issue is only apparent on native iOS devices鈥攖here is no issue in the dev tools simulator (tested on Chrome).

The position: fixed trick resolves the issue of having the body content scrollable when the modal is open for mobile iOS, but it may introduce new bugs in the process. One that affected me was not having the body element's width set to 100%, so my content was "squished" left whenever a modal was opened, which looked pretty strange.

I am unsure if there are other bugs introduced with this workaround, and perhaps that was just shame-on-me for not having my body width defined, but I thought I'd throw it out there in case other people share the same issue.

(Is it just me who feels like we're repeating history with some of this? Haha.)

I'm having the same problem position: fixed doesn't solve the problem when:
a user scrolls halfway down a long page
opens then modal
closes the modal

applying position: fixed in this case will cause the user to scroll to the top of the page.
Our resolution is to track the scroll position on each modal that we use with

window.scrollY and window.scrollTo(0, y)

class Modal extends Component {
  state = { scrollPosition: 0 }

  componentDidMount() {
    this.setState({ scrollPosition: window.scrollY });
    document.body.style.position = 'fixed';
    document.body.style.overflow = 'hidden';
  }

  componentDidUnmount() {
    document.body.style.position = 'initial';
    document.body.style.overflow = 'initial';
    window.scrollTo(0, this.state.scrollPosition);
  }

  render() {...}
}

perhaps there's a way to dry this up into a HoC/prop flag or something and incorporate it with the library so that more people can benefit from this in an opt-in fashion?

I've tested a lightweight workaround, while it's not inside react-modal package, may be it could help someone.

  const preventIOSScroll = (e) => {
    e.preventDefault();
  }

  <Modal
      isOpen={showModal}
      contentLabel="Modal"
      onAfterOpen={() => {
        document.documentElement.style.overflowY = 'hidden';
        document.documentElement.addEventListener('touchmove', preventIOSScroll);
      }}
      onRequestClose={() => {
        document.documentElement.style.overflowY = 'visible';
        document.documentElement.removeEventListener('touchmove', preventIOSScroll);
      }}
      shouldCloseOnOverlayClick
      style={({ overlay: { zIndex: 100, backgroundColor: 'rgba(0, 0, 0, 0.4)' } })}
  >
    <YourComponentsInsideModal />
  </Modal>

I like to move this logic inside static methods, but you could place it as described before. Tested with almost all accessible iOS devices within SimulatorApp.

I've tested a lightweight workaround, while it's not inside react-modal package, may be it could help someone.

  const preventIOSScroll = (e) => {
    e.preventDefault();
  }

  <Modal
      isOpen={showModal}
      contentLabel="Modal"
      onAfterOpen={() => {
        document.documentElement.style.overflowY = 'hidden';
        document.documentElement.addEventListener('touchmove', preventIOSScroll);
      }}
      onRequestClose={() => {
        document.documentElement.style.overflowY = 'visible';
        document.documentElement.removeEventListener('touchmove', preventIOSScroll);
      }}
      shouldCloseOnOverlayClick
      style={({ overlay: { zIndex: 100, backgroundColor: 'rgba(0, 0, 0, 0.4)' } })}
  >
    <YourComponentsInsideModal />
  </Modal>

I like to move this logic inside static methods, but you could place it as described before. Tested with almost all accessible iOS devices within SimulatorApp.

Didn't initially work for me. After some investigation I figured out that now you need to pass { passive: false } as a third argument to addEventListener:

document.documentElement.addEventListener('touchmove', function(e) {
    e.preventDefault();
}, { passive: false });

Check here for details:
https://stackoverflow.com/questions/49500339/cant-prevent-touchmove-from-scrolling-window-on-ios

Was this page helpful?
0 / 5 - 0 ratings