2

I am using react-native-reanimated and react-native-gesture-handler to create a view that allows you to "explore" what's inside of it (even if it exceeds its width and height).

Here's my gestureHandler that updates translationX & translationY variables, later used in useAnimatedStyle to "move" the <Animated.View>:

const gestureHandler = useAnimatedGestureHandler({
  onStart: (_, ctx) => {
    ctx.startX = translationX.value;
    ctx.startY = translationY.value;
  },
  onActive: (event, ctx) => {
    'worklet';
    translationX.value = ctx.startX + event.translationX;
    translationY.value = ctx.startY + event.translationY;

    // update state to view values on the screen as they change
    runOnJS(setPosition)({ x: translationX.value, y: translationY.value });
  },
  onEnd: () => {
    'worklet';
    const boundedX = clamp(
      translationX.value,
      (BOX_WIDTH - container?.width) * -1,
      0
    );
    const boundedY = clamp(
      translationY.value,
      (BOX_HEIGHT - container?.height) * -1,
      0
    );

    // create "bounce-y" effect when moving the box back inside the bounds
    translationX.value = withTiming(boundedX);
    translationY.value = withTiming(boundedY);

    // update state to view values on the screen as they change
    runOnJS(setPosition)({ x: boundedX, y: boundedY });
  },
});

This code "works" in the following case:

  • I have a "visible area" of width: 100, height: 100
  • "box" (element that is being panned) of width: 160, height: 160

Here's a gif (click to view in full size):

I created an example as a Expo Snack displaying my problem. If you change INITIAL_SCALE in Transforms.js to 0.5 or just tap the pink box (it changes its scale to NEW_SCALE, see onPress()), panning in boundaries no longer works.

Ivanka Todorova
  • 9,964
  • 16
  • 66
  • 103

1 Answers1

2

I found the problem, it was scaling the box using its origin (the center), so before applying the scale transform, I had to translate it to "fake" setting its origin to the top left corner.

{ translateX: -BOX_WIDTH / 2 },
{ translateY: -BOX_HEIGHT / 2 },
{ scale: scale.value }, // <- NOW I am changing the scale of the box
{ translateX: BOX_WIDTH / 2 },
{ translateY: BOX_HEIGHT / 2},

I also made a function for calculating the "edges" of the outer box that have in mind the pink box's scale:

const getEdges = () => {
  'worklet';
  const pointX =
    (BOX_WIDTH * scale.value - VISIBLE_AREA_WIDTH) * -1;
  const pointY =
    (BOX_HEIGHT * scale.value - VISIBLE_AREA_HEIGHT) * -1;
  return {
    x: {
      min: Math.min(pointX, 0),
      max: Math.max(0, pointX),
    },
    y: {
      min: Math.min(pointY, 0),
      max: Math.max(0, pointY),
    },
  };
};

Here's a working Snack with these fixes.

Helpful information:

Ivanka Todorova
  • 9,964
  • 16
  • 66
  • 103