Skip to content

[Web] Continuous Pinch and Rotation#4155

Open
m-bert wants to merge 7 commits intomainfrom
@mbert/continuous-pinch-rotation
Open

[Web] Continuous Pinch and Rotation#4155
m-bert wants to merge 7 commits intomainfrom
@mbert/continuous-pinch-rotation

Conversation

@m-bert
Copy link
Copy Markdown
Collaborator

@m-bert m-bert commented May 7, 2026

Description

This PR changes Pinch and Rotation on web so that they're not cancelled after releasing one pointer.

Test plan

Tested on Transformations example.

Also tested on the following code:
import React from 'react';
import { StyleSheet, View } from 'react-native';
import {
  GestureDetector,
  usePinchGesture,
  useRotationGesture,
  useSimultaneousGestures,
} from 'react-native-gesture-handler';
import Animated, {
  useAnimatedStyle,
  useSharedValue,
} from 'react-native-reanimated';

export default function EmptyExample() {
  const scale = useSharedValue(1);
  const rotation = useSharedValue(0);

  const pinch = usePinchGesture({
    onBegin: () => {
      console.log('[Pinch] onBegin');
    },
    onActivate: () => {
      console.log('[Pinch] onActivate');
    },
    onUpdate: (e) => {
      console.log('[Pinch] onUpdate scaleChange:', e.scaleChange);
      scale.value *= e.scaleChange;
    },
    onDeactivate: () => {
      console.log('[Pinch] onDeactivate');
    },
    onFinalize: () => {
      console.log('[Pinch] onFinalize');
    },
  });

  const rotate = useRotationGesture({
    onBegin: () => {
      console.log('[Rotation] onBegin');
    },
    onActivate: () => {
      console.log('[Rotation] onActivate');
    },
    onUpdate: (e) => {
      console.log('[Rotation] onUpdate rotationChange:', e.rotationChange);
      rotation.value += e.rotationChange;
    },
    onDeactivate: () => {
      console.log('[Rotation] onDeactivate');
    },
    onFinalize: () => {
      console.log('[Rotation] onFinalize');
    },
  });

  const gesture = useSimultaneousGestures(pinch, rotate);

  const animatedStyle = useAnimatedStyle(() => ({
    transform: [
      { scale: scale.value },
      { rotate: `${(rotation.value * 180) / Math.PI}deg` },
    ],
  }));

  return (
    <View style={styles.container}>
      <GestureDetector gesture={gesture}>
        <Animated.View style={[styles.circle, animatedStyle]}>
          <View style={styles.indicator} />
        </Animated.View>
      </GestureDetector>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  circle: {
    width: 300,
    height: 300,
    borderRadius: 150,
    backgroundColor: '#b58df1',
    justifyContent: 'center',
    alignItems: 'center',
  },
  indicator: {
    width: 6,
    height: '50%',
    borderRadius: 3,
    backgroundColor: 'rgba(255, 255, 255, 0.5)',
    position: 'absolute',
    top: 0,
  },
});

Copilot AI review requested due to automatic review settings May 7, 2026 13:29
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the web implementations of Pinch and Rotation to remain active when one pointer is lifted (i.e., not cancel/end on ADDITIONAL_POINTER_UP), enabling “continuous” transform gestures across pointer changes.

Changes:

  • Update PinchGestureHandler and RotationGestureHandler to keep pointer tracking up to date before early-returning when fewer than 2 pointers are present.
  • Stop ending an active pinch on onPointerRemove (non-last pointer up).
  • Adjust web gesture detectors (ScaleGestureDetector, RotationGestureDetector) to avoid finishing the gesture when a non-last pointer is lifted.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
packages/react-native-gesture-handler/src/web/handlers/RotationGestureHandler.ts Track pointer movement earlier so positions remain current even when <2 pointers.
packages/react-native-gesture-handler/src/web/handlers/PinchGestureHandler.ts Keep pinch ACTIVE across onPointerRemove and track earlier on move/out-of-bounds.
packages/react-native-gesture-handler/src/web/detectors/ScaleGestureDetector.ts Change “stream complete” conditions to not treat ADDITIONAL_POINTER_UP as completion.
packages/react-native-gesture-handler/src/web/detectors/RotationGestureDetector.ts Reset rotation detector state on key pointer lift instead of finishing (ending).
Comments suppressed due to low confidence (1)

packages/react-native-gesture-handler/src/web/detectors/ScaleGestureDetector.ts:52

  • ScaleGestureDetector.onTouchEvent no longer treats EventTypes.ADDITIONAL_POINTER_UP as streamComplete, so it continues executing the rest of the method on pointer-up. In the common 2-pointer pinch case this leads to configChanged being true with div = numOfPointers - 1, producing span = 0 and (because wasInProgress is true) immediately calling onScaleBegin again on the pointer-up event. That resets prevTime/currentSpan in a way that can skew the first subsequent timeDelta/velocity and makes the detector appear "in progress" while only 1 pointer remains. Consider restoring EventTypes.ADDITIONAL_POINTER_UP to the streamComplete condition (and keep the continuous behavior in PinchGestureHandler by not ending on pointer-remove), or otherwise early-return after handling ADDITIONAL_POINTER_UP so onScaleBegin cannot be re-entered on pointer-up.
    const numOfPointers = tracker.trackedPointersCount;

    const streamComplete: boolean =
      action === EventTypes.UP || action === EventTypes.CANCEL;

    if (action === EventTypes.DOWN || streamComplete) {
      if (this.inProgress) {
        this.onScaleEnd(this);

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@m-bert
Copy link
Copy Markdown
Collaborator Author

m-bert commented May 7, 2026

Return on pointer up addresses suppressed @copilot comment.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment thread packages/react-native-gesture-handler/src/web/detectors/ScaleGestureDetector.ts Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

@m-bert m-bert requested a review from j-piasecki May 7, 2026 14:49
@m-bert m-bert changed the title [Web] Continuous Pinch and Rotation on web [Web] Continuous Pinch and Rotation May 7, 2026
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

Successfully merging this pull request may close these issues.

3 participants