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

Swiping too hard causes the whole viewport unclickable #676

Open
2 tasks done
vincentsartoko opened this issue Jul 21, 2024 · 8 comments
Open
2 tasks done

Swiping too hard causes the whole viewport unclickable #676

vincentsartoko opened this issue Jul 21, 2024 · 8 comments
Assignees

Comments

@vincentsartoko
Copy link

vincentsartoko commented Jul 21, 2024

Describe the bug
When using useDrag where velocity is greater than 1, there is a moment where you cannot click anywhere else. The behaviour is similar to clicking a shadow overlay that doesn't register anything, and everything seems fine after you click this shadow overlay.
This also happens on the official site of use-gesture, take a look at the video below.

Sandbox or Video
Here is the bug on the use-gesture website:

bug.mp4

Here is the detailed console.log on my app:

import { useSpring, animated } from '@react-spring/web';
import { useDrag } from '@use-gesture/react';

export default function Component() {
  const [{ x, y }, api] = useSpring(() => ({ x: 0, y: 0 }));

  const bind = useDrag(({ down, movement: [mx, my], velocity }) => {
    if (!down) console.log(`Click inside useDrag with velocity: ${velocity}`); // Log inside useDrag
    api.start({ x: down ? mx : 0, y: down ? my : 0, immediate: down });
  });

  return (
    <div>
      <animated.div
        {...bind()}
        onClick={() => console.log('Click inside onClick')} // Log when click
        style={{ x, y, width: 100, height: 100, background: 'red', touchAction: 'none' }}
      />
    </div>
  );
}
bug1.mp4

Information:

  • Use Gesture version: 10.3.1
  • Device: Mobile web / Webview
  • Browser: Google Chrome

Checklist:

  • I've read the documentation.
  • If this is an issue with drag, I've tried setting touch-action: none to the draggable element.
@dbismut
Copy link
Collaborator

dbismut commented Jul 21, 2024

Are you able to reproduce this outside of the inspector (ie real device)?

@vincentsartoko
Copy link
Author

Are you able to reproduce this outside of the inspector (ie real device)?

Yes, I deployed on production as a webapp, and an Android webview through CapacitorJS.

@dbismut
Copy link
Collaborator

dbismut commented Jul 23, 2024

The example from the docs you're referring to and showing in the issue: are you able to reproduce this on a real device with a normal browser?

@vincentsartoko
Copy link
Author

@dbismut Yes I could reproduce it on my phone, and some of our users. How about from your side?

@dbismut
Copy link
Collaborator

dbismut commented Jul 23, 2024

No, I can't, hence why I'm asking. I'm using an iPhone 15 Pro and Safari. Reason why I'm asking about context and devices is because I think it might be related to pointer capture but there's no reason why it wouldn't work normally on regular devices.

@vincentsartoko
Copy link
Author

@dbismut all occurances happened on Android phones, FYI I use Samsung S22 ultra.

@teleoflexuous
Copy link

teleoflexuous commented Sep 19, 2024

I am experiencing the same issue on both web Chrome with devtools and multiple Android (Samsung) devices in Chrome. It works fine on Firefox.

As far as I can tell it's not an issue with the library, but with adding touchAction to an element, @vincentsartoko. I'm still mentioning it here, since that's the only mention of the issue I've found so far and maybe next person finds a solution.

@teleoflexuous
Copy link

teleoflexuous commented Sep 20, 2024

Here's the best hack I found, slightly modified from solution posted in the thread on Chromium issues forum started mere 4 years ago.

  const fakeClick = React.useRef<boolean>(false);

  const fakeMouseClickToOvercomeChromeBug = () => {
        fakeClick.current = true;
        const handleTouchstart = (touchStartEvent: TouchEvent) => {
            const startTouch = touchStartEvent.touches.item(0);
            if (!startTouch || touchStartEvent.touches.length > 1) {
                return;
            }
            const startX = startTouch.pageX;
            const startY = startTouch.pageY;

            let isValid = true;
            const handleTouchmove = (e: TouchEvent) => {
                const touch = e.touches.item(0);
                if (!touch || e.touches.length > 1
                    || Math.sqrt(((touch.pageX - startX) ** 2) + ((touch.pageY - startY) ** 2)) > 5) {
                    isValid = false;
                    window.removeEventListener('touchmove', handleTouchmove);
                }
            };

            const handleTouchend = (e: TouchEvent) => {
                const { target } = e;
                if (!isValid || !target) {
                    return;
                }

                e.preventDefault();
                setTimeout(() => {
                    target.dispatchEvent(new MouseEvent('click', {
                        bubbles: true,
                        cancelable: true,
                    }));
                }, 0);
            };

            const handleMouseDown = () => {
                window.removeEventListener('touchmove', handleTouchmove);
                window.removeEventListener('touchend', handleTouchend);
                window.removeEventListener('mousedown', handleMouseDown);
            };


            window.addEventListener('touchmove', handleTouchmove);
            window.addEventListener('touchend', handleTouchend, { once: true });
            window.addEventListener('mousedown', handleMouseDown, { once: true });

            setTimeout(() => {
                window.removeEventListener('touchmove', handleTouchmove);
                window.removeEventListener('touchend', handleTouchend);
                window.removeEventListener('mousedown', handleMouseDown);
            }, CLICK_MAX_WAIT);
        };

        setTimeout(() => {
            if (fakeClick.current) {
                fakeClick.current = false;
                window.addEventListener('touchstart', handleTouchstart);
            }
        }, 50);
    }

It stops the problem from interrupting user experience and I hate it.

Given prevalence of Chromium I believe it may be worth at least mentioning in docs alongside explanation of touch-action role.

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

3 participants