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:
width: 100, height: 100
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.
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: