reactjs / react-modal

Accessible modal dialog component for React
http://reactcommunity.org/react-modal
MIT License
7.37k stars 809 forks source link

background element under overlay is draggable when modal is opened in ios safari #369

Open chuanxie opened 7 years ago

chuanxie commented 7 years ago

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

0xashu commented 7 years ago

@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',
    },
  }}
>
musicalshore commented 7 years ago

I, too, am encountering this problem.

diasbruno commented 7 years ago

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

musicalshore commented 7 years ago

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

indiesquidge commented 7 years ago

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—there 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.)

wgottschalk commented 7 years ago

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?

vadimshvetsov commented 7 years ago

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.

semeleven commented 6 years ago

position: fixed nice solution. In https://github.com/robinparisi/tingle it is work - https://github.com/robinparisi/tingle/commit/cf389d32ecaaccfa1452db2e08c73f8d2602739b, https://github.com/robinparisi/tingle/commit/0bc1b5d748775eb10aa40ea87d1e5dd52d1c5c9d

yuzhakovvv commented 5 years ago

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