react-native-gesture-handler icon indicating copy to clipboard operation
react-native-gesture-handler copied to clipboard

LongPress not working on web

Open elliotwaite opened this issue 3 years ago • 5 comments

Description

Not all LongPress callbacks are being called on web.

Platforms

  • [ ] iOS
  • [ ] Android
  • [x] Web

Screenshots

long-press-screen-recording

Steps To Reproduce

Run the code below and press the red circle.

Expected behavior

For all these callbacks to be called:

  • onTouchesDown
  • onBegin
  • onStrart
  • onTouchesUp
  • onEnd
  • onFinalize

Actual behavior

Only these callbacks are called:

  • onEnd
  • onFinalize

Snack or minimal code example

import { Platform, StyleSheet, Text } from 'react-native'
import {
  Gesture,
  GestureDetector,
  GestureHandlerRootView,
} from 'react-native-gesture-handler'
import Animated, {
  useAnimatedStyle,
  useSharedValue,
} from 'react-native-reanimated'

export default function App() {
  const isPressed = useSharedValue(false)
  const onTouchesDownWasCalled = useSharedValue(false)
  const onBeginWasCalled = useSharedValue(false)
  const onStartWasCalled = useSharedValue(false)
  const onTouchesUpWasCalled = useSharedValue(false)
  const onEndWasCalled = useSharedValue(false)
  const onFinalizeWasCalled = useSharedValue(false)

  const longPressGesture = Gesture.LongPress()
    .onTouchesDown(() => {
      isPressed.value = true
      onTouchesDownWasCalled.value = true
      onBeginWasCalled.value = false
      onStartWasCalled.value = false
      onTouchesUpWasCalled.value = false
      onEndWasCalled.value = false
      onFinalizeWasCalled.value = false
    })
    .onBegin(() => {
      onBeginWasCalled.value = true
    })
    .onStart(() => {
      onStartWasCalled.value = true
    })
    .onTouchesUp(() => {
      onTouchesUpWasCalled.value = true
    })
    .onEnd(() => {
      onEndWasCalled.value = true
    })
    .onFinalize(() => {
      isPressed.value = false
      onFinalizeWasCalled.value = true
    })

  const onTouchesDownStyles = useAnimatedStyle(() => ({
    opacity: onTouchesDownWasCalled.value ? 1 : 0,
  }))

  const onBeginStyles = useAnimatedStyle(() => ({
    opacity: onBeginWasCalled.value ? 1 : 0,
  }))

  const onStartStyles = useAnimatedStyle(() => ({
    opacity: onStartWasCalled.value ? 1 : 0,
  }))

  const onTouchesUpStyles = useAnimatedStyle(() => ({
    opacity: onTouchesUpWasCalled.value ? 1 : 0,
  }))

  const onEndStyles = useAnimatedStyle(() => ({
    opacity: onEndWasCalled.value ? 1 : 0,
  }))

  const onFinalizeStyles = useAnimatedStyle(() => ({
    opacity: onFinalizeWasCalled.value ? 1 : 0,
  }))

  const ballAnimatedStyles = useAnimatedStyle(() => ({
    backgroundColor: isPressed.value ? 'yellow' : 'red',
  }))

  return (
    <GestureHandlerRootView style={styles.container}>
      <Text style={styles.text}>
        {Platform.OS === 'web' ? 'Web Version' : 'Native Version'}
      </Text>
      <Animated.View style={onTouchesDownStyles}>
        <Text style={styles.text}>`onTouchesDown()` was called.</Text>
      </Animated.View>
      <Animated.View style={onBeginStyles}>
        <Text style={styles.text}>`onBegin()` was called.</Text>
      </Animated.View>
      <Animated.View style={onStartStyles}>
        <Text style={styles.text}>`onStart()` was called.</Text>
      </Animated.View>
      <Animated.View style={onTouchesUpStyles}>
        <Text style={styles.text}>`onTouchesUp()` was called.</Text>
      </Animated.View>
      <Animated.View style={onEndStyles}>
        <Text style={styles.text}>`onEnd()` was called.</Text>
      </Animated.View>
      <Animated.View style={onFinalizeStyles}>
        <Text style={styles.text}>`onFinalize()` was called.</Text>
      </Animated.View>
      <GestureDetector gesture={longPressGesture}>
        <Animated.View style={[styles.ball, ballAnimatedStyles]} />
      </GestureDetector>
    </GestureHandlerRootView>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  text: {
    paddingBottom: 16,
    fontSize: 16,
  },
  ball: {
    width: 128,
    height: 128,
    borderRadius: 64,
  },
})

Package versions

  • React: 17.0.1
  • React Native: 0.64.3
  • React Native Gesture Handler: 2.2.1
  • React Native Reanimated: 2.3.3

elliotwaite avatar May 18 '22 19:05 elliotwaite

Hi! The linked PR should fix most of the issues. What's still not fixed are touch events (onTouches...), as that will require more research to see if implementing it using hammerjs is feasible.

j-piasecki avatar May 19 '22 09:05 j-piasecki

Sounds good. Thanks for the update.

elliotwaite avatar May 19 '22 14:05 elliotwaite

Also, I noticed that manual gestures currently aren't supported on web. I get this error message: Error: react-native-gesture-handler: ManualGestureHandler is not supported on web.

Is this related to the same issue with the touch events? Once the touch events are supported, will this make it possible to also support manual gestures on web?

elliotwaite avatar May 19 '22 14:05 elliotwaite

That's right. Without touch events, manual gestures are pretty much useless since those are the only type of events they send without manually managing state, which can be done only inside touch event callbacks.

j-piasecki avatar May 20 '22 07:05 j-piasecki

Okay, makes sense. Thanks for the reply.

elliotwaite avatar May 20 '22 15:05 elliotwaite

Any updates on this issue/PR? Facing the same problem on web.

chaaya avatar Aug 22 '22 08:08 chaaya

Hey! 👋

The issue doesn't seem to contain a minimal reproduction.

Could you provide a snack or a link to a GitHub repository under your username that reproduces the problem?

github-actions[bot] avatar Aug 22 '22 08:08 github-actions[bot]

Hi 👋 This issue should be fixed in 2.6.1

Note that this change is available in new web implementation. To enable it, call enableExperimentalWebImplementation() in the root of your project.

m-bert avatar Sep 21 '22 13:09 m-bert

@Warus15, thanks for letting me know. Sorry for the delay in my reply. I just got around to testing out the latest version (2.7.0) with enableExperimentalWebImplementation(true), and I can confirm that all callbacks are now being called on both native and web.

However, I just noticed that the order of the events is different between platforms.

On Android, the order is:

  • onBegin
  • onTouchesDown
  • onStart
  • onTouchesUp
  • onEnd
  • onFinalize

On iOS and web, the order is:

  • onTouchesDown (called before onBegin)
  • onBegin
  • onStart
  • onTouchesUp
  • onEnd
  • onFinalize

So it seems like the new web version is aligned with how iOS does it.

Because of this difference in order, I had to update my example code above to get it to work correctly across platforms since I was originally resetting the values in the onBegin callback, which I was incorrectly assuming would always be called first after testing it on Android (note: I was originally only testing it on Android and web, so I hadn't previously noticed the difference in behavior on iOS).

Given that this difference in order could lead to bugs, as it did in my example code above, do you think this difference should be fixed so that all platforms use the same order? If so, I can open a new issue about this. But I'll close this issue since this new issue is different than the original one.

elliotwaite avatar Oct 07 '22 06:10 elliotwaite

Hi @elliotwaite 👋 Thanks for letting me know! I'm glad to hear that new implementation fixes this issue.

In web implementation order of events looks exactly how we wanted it to look like, so if one platform is inconsistent with the others, it would be nice if you could open new issue, so we can have a look at that.

m-bert avatar Oct 07 '22 09:10 m-bert

Okay, sounds good, I created the new issue here: https://github.com/software-mansion/react-native-gesture-handler/issues/2263

elliotwaite avatar Oct 07 '22 13:10 elliotwaite