pradel / react-responsive-modal

Simple responsive react modal
https://react-responsive-modal.leopradel.com/
MIT License
606 stars 95 forks source link

Potential iOS scroll bug #468

Open O4epegb opened 3 years ago

O4epegb commented 3 years ago

Hi, everyone! First of all, thanks for nice library, great job!

Bug report

Describe the bug

I've encountered strange problem with scroll, not quite sure if this is because of some weird bug or maybe I am doing something wrong, or it just Safari css behaviour.

Anyway, so if you make full screen modal with css flexbox then it won't let you scroll content inside of it on iOS. It works fine on Desktop (Mac, Window), have not tested on Android.

I saw an issue about scroll and I waited for the fix, but it did not fixed it actually. https://github.com/pradel/react-responsive-modal/issues/462

And if you disable scroll block (blockScroll={false}) then it actually works, but not quite good because, well, body scroll is not disabled and it is wonky.

To Reproduce

Open modal on iOS, try to scroll

Expected behavior

You can scroll to the end

Screenshots

Codesandbox: https://xop8k.csb.app/

System information

genox commented 3 years ago

Similar issue. Trying to scroll on a video element which has a div element stretched over it (pos absolute), horizontally within a mobile viewport. The peculiar thing is that sometimes I can scroll as I would expect, but as soon as the movement/slow ease out of the scroll animation stops, scrolling is not possible anymore for a while. Then, when quickly trying to move the element, sometimes it "grabs" the element again and scrolls as desired.

The same approach works fine on desktop browsers, so I think it's an iOS webkit specific issue - or touch event related.

pradel commented 3 years ago

As I don't have an IOS device I can't debug and fix this issue. If anyone wants to give it a try I would gladly accept a pr to fix this :)

genox commented 3 years ago

Thanks for your reply. I know this is a tricky one and honestly, I don't even know where to start looking for the root cause. And with these odds, for now I'll just stick to a workaround. What I was able to figure out is basically the same as OP: If the body scroll is locked on iOS Safari, scroll within the modal is (randomly, for some reason) locked, too.

From past experiences with issues on scroll locking, I know that iOS Safari is very anal about various techniques and - but don't pin me down on that - it might have something to do with touch event propagation, in which case we can't do anything, anyways. I ended up targeting iOS Safari with a dirty hack and set blockScroll={!isIosSafari()}. This basically solves the issue for me.

If you would like access to an iOS testing environment, I can share some time on BrowserStack with you. Let me know. But for now I'd file it under "things that define my hate love with iOS". ;-)

pradel commented 3 years ago

To block the scroll we use the body-scroll-lock npm package and by just taking a look at the IOS issues I can see that IOS seems to be a constant source of pain :D https://github.com/willmcpo/body-scroll-lock/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc So the bug might actually be on this lib, making it even harder to debug.

genox commented 3 years ago

Oh.. oh.. now all the compartmentalised and suppressed memories come back.. exactly. I was playing around with this lib a few years ago and ended up in the exact same rabbit hole. Oh well. Maybe just document the iOS issue potential and suggest browser or feature detection as a workaround then. Don't sink your time into this.

pc035860 commented 3 years ago

I fork the example at https://2wyki.csb.app/ with height: 100% in styles.css commented out, and it works on my iOS 14.4.2 Safari.

It seems that body-scroll-lock requires setting disableBodyScroll on correct scroll container. For the original example https://xop8k.csb.app/, actually, v6.0.0 will work, since it calls disableBodyScroll on modal rather than modal container.

Working version uses v6.0.0: https://qedbk.csb.app/ (only change the react-responsive-modal version)

In conclusion, the scroll lock behavior highly depends on how you write your CSS, hence there are a lot of issues reported around body-scroll-lock package.

bhj commented 3 years ago

A potential fix has been merged at https://github.com/willmcpo/body-scroll-lock/pull/207; perhaps it's worth releasing a beta with it?

Link2Twenty commented 3 years ago

I've downgraded back to 5.1.1 for now as that doesn't seem to have to the issue.

bhj commented 2 years ago

There is another PR at https://github.com/willmcpo/body-scroll-lock/pull/229 that fixes some additional issues; just posting here for reference. Hopefully when that is merged and released it can be incorporated into this package.

Feijo commented 1 year ago

Any updates on this?

Kepro commented 1 year ago

Any updates on this?

https://github.com/willmcpo/body-scroll-lock/pull/229#issuecomment-1040498731

Feijo commented 1 year ago

Given this lib is kinda dead, if you need a simple replacement, here it goes

import { css } from '@emotion/react';
import { MouseEvent, useEffect, useRef } from 'react';
import { GrClose } from 'react-icons/gr';

const styles = {
  root: css`
    width: 400px;
    border-radius: 8px;
    border: 1px solid #888;

    ::backdrop {
      background: rgba(0, 0, 0, 0.3);
    }
  `,
  close: css`
    position: absolute;
    right: 15px;
  `,
};

const isClickInsideRectangle = (e: MouseEvent, element: HTMLElement) => {
  const r = element.getBoundingClientRect();

  return e.clientX > r.left && e.clientX < r.right && e.clientY > r.top && e.clientY < r.bottom;
};

type Props = {
  title?: string;
  open: boolean;
  showCloseIcon?: boolean;
  onClose: () => void;
  children: React.ReactNode;
};

const Modal = ({ title, open, showCloseIcon, onClose, children }: Props) => {
  const ref = useRef<HTMLDialogElement>(null);

  useEffect(() => {
    if (open) {
      ref.current?.showModal();
      document.body.classList.add('modal-open'); // Add this to your main styles file: body.modal-open { overflow: hidden; }
    } else {
      ref.current?.close();
      document.body.classList.remove('modal-open');
    }
  }, [open]);

  return (
    <dialog
      ref={ref}
      css={styles.root}
      onCancel={onClose}
      onClick={(e) => ref.current && !isClickInsideRectangle(e, ref.current) && onClose()}
    >
      {showCloseIcon !== false && (
        <div css={styles.close} onClick={onClose}>
          <GrClose />
        </div>
      )}

      <h3>{title}</h3>

      {children}
    </dialog>
  );
};

export default Modal;