react-native-gesture-handler
react-native-gesture-handler copied to clipboard
Gesture.Tap().maxDuration(...) not working on web
Description
Using maxDuration()
on a Tap gesture doesn't seem to work on web.
Platforms
- [ ] iOS
- [ ] Android
- [x] Web
Screenshots
Steps To Reproduce
Run the code example below on the web then tap and hold on the red circle.
Expected behavior
For it to behave the same as on native where if the tap is held longer than the maxDuration
value, the onTouchesCancelled
callback is called followed by the onFinalize
callback being called.
Actual behavior
On web, the tap can be held forever without ever being canceled.
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 wasCancelled = useSharedValue(false)
const wasFinalized = useSharedValue(false)
const tapGesture = Gesture.Tap()
.maxDuration(500)
.onBegin(() => {
isPressed.value = true
wasCancelled.value = false
wasFinalized.value = false
})
.onTouchesCancelled(() => {
wasCancelled.value = true
})
.onFinalize(() => {
isPressed.value = false
wasFinalized.value = true
})
const wasCancelledAnimatedStyles = useAnimatedStyle(() => ({
opacity: wasCancelled.value ? 1 : 0,
}))
const wasFinalizedAnimatedStyles = useAnimatedStyle(() => ({
opacity: wasFinalized.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={wasCancelledAnimatedStyles}>
<Text style={styles.text}>`onTouchesCancelled()` was called.</Text>
</Animated.View>
<Animated.View style={wasFinalizedAnimatedStyles}>
<Text style={styles.text}>`onFinalize()` was called.</Text>
</Animated.View>
<GestureDetector gesture={tapGesture}>
<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
It's similar situation to https://github.com/software-mansion/react-native-gesture-handler/issues/2063#issuecomment-1131475881. If you want to know whether the gesture ended successfully or was cancelled you can use the second argument of onEnd
and onFinalize
callbacks, for example:
const tap = Gesture.Tap()
.onBegin(() => console.log('begin'))
.onStart(() => console.log('start'))
.onEnd((_e, success) => console.log('end, successful:', success))
.onFinalize((_e, success) => console.log('finalize, successful:', success));
Okay, good to know. Thanks!
Hi 👋 This issue should be fixed in 2.6.1. What's left is that onTouchesCancelled
callback is not being triggered. You can try this PR and everything should work fine.
Note that this change is available in new web implementation. To enable it, call enableExperimentalWebImplementation()
in the root of your project.
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?
@Warus15, thanks for letting me know. I just got around to testing out the latest version (2.7.0) with enableExperimentalWebImplementation(true)
, and I can confirm that it now works as expected on the web, so I'll close this issue.
However, I did notice that a holding a tap longer than the the max duration results in a slight difference in behavior between the platforms:
On Android and web, the callbacks called are:
- onBegin
- onTouchesCancelled
- onFinalize
On iOS, the callbacks called are:
- onBegin
- onTouchesCancelled
- onFinalize
- onFinalize (called a second time)
The second call to onFinalize
on iOS only occurs when the max duration is exceeded. It doesn't occur if the tap is released before reaching the max duration (in which case it's only called a single time all on platforms). Do think this difference in behavior should be fixed? If so, I can open a separate issue about this.
Hi @elliotwaite! I'm happy to hear that new implementation solves this issue. Also it would be nice if you could open issue for this double onFinalize
callback, because with repro it would be easier to see whether that's something that we should worry about.
Okay, sounds good, I created the new issue here: https://github.com/software-mansion/react-native-gesture-handler/issues/2263