skip to Main Content

This was before I realized onStart() wasn’t being called

Using PanGestureHandler and attempting to "drag" an AnimatedView is not working on the web. There are no obvious bugs, the app builds just fine, no warnings in the console when inspecting.

There is one warning that is leading me to believe this could be the source of the problem. I get a warning in the console saying this:

"transform" style array value is deprecated. Use space-separated string functions, e.g., "scaleX(2) rotateX(15deg)".

I am using an AnimatedView with styling from containerStyle to transform the object and move it around when it is being dragged.

The Root of the Problem

So I’ve been looking into it further, trying to debug it and I’ve come to realize the onStart() callback is not being called. Since the onStart() callback isn’t being called the context values never end up getting set and the context object overall remains empty. Leading to my initial problem of not being able to drag the object.

This still does work on iOS though. For some reason, on iOS the onStart() callback is called. This leads to the context being filled and it working.

This is my code so far, keep in mind this is just a component. In the root, I do have a GestureHandlerRootView component wrapping the whole app.

import { View, Image } from 'react-native';
import Animated, {
    useAnimatedStyle,
    useSharedValue,
    useAnimatedGestureHandler,
    withSpring,
} from 'react-native-reanimated';
import { PanGestureHandler, TapGestureHandler } from 'react-native-gesture-handler';

const AnimatedImage = Animated.createAnimatedComponent(Image);
const AnimatedView = Animated.createAnimatedComponent(View);

export default function EmojiSticker ({ imageSize, stickerSource }) {
    const scaleImage = useSharedValue(imageSize);
    const translateX = useSharedValue(0);
    const translateY = useSharedValue(0);

    const onDoubleTap = useAnimatedGestureHandler({
        onActive: () => {
            if (scaleImage.value !== imageSize * 2) {
                scaleImage.value = scaleImage.value * 2;
            } else {
                scaleImage.value = scaleImage.value / 2;
            }
        },
    });
    const onDrag = useAnimatedGestureHandler({
        onStart: (event, context) => {
            context.translateX = translateX.value;
            context.translateY = translateY.value;
        },
        onActive: (event, context) => {
            translateX.value = event.translationX + context.translateX;
            translateY.value = event.translationY + context.translateY;
        },
    });
    
    const imageStyle = useAnimatedStyle(() => {
        return {
            width: withSpring(scaleImage.value),
            height: withSpring(scaleImage.value),
        };
    });
    const containerStyle = useAnimatedStyle(() => {
        return {
            transform: [
                {
                    translateX: translateX.value,
                },
                {
                    translateY: translateY.value,
                },
            ],
        };
    });

    return (
        <PanGestureHandler onGestureEvent={onDrag}>
            <AnimatedView style={[containerStyle, { top: -350 }]}>
                <TapGestureHandler onGestureEvent={onDoubleTap} numberOfTaps={2}>
                    <AnimatedImage 
                        source={stickerSource}
                        resizeMode='contain'
                        style={[imageStyle, { width: imageSize, height: imageSize }]}
                    />
                </TapGestureHandler>
            </AnimatedView>
        </PanGestureHandler>
    );
}

By the way the double tap gesture works perfectly both on the web and iOS.
I am stumped because the dragging works perfectly in iOS just not on the Web. The transform style depreciation has led me to try and figure out a way to create web-specific styling but I’m having a hard time finding anyone else having this issue. I’m sure there’s a real solution to this that I’m just missing. I’m just really confused since it works perfectly on iOS but not on the Web.

I tried to see if anybody else had any issues relating to this and couldn’t really find anything. I also tried looking up the warning I saw in the console.

"transform" style array value is deprecated. Use space-separated string functions, e.g., "scaleX(2) rotateX(15deg)".

I didn’t find anything relating to this either at least when I searched relating it to React-Native

I’m hoping for a solution that makes it draggable on the web.

2

Answers


  1. Chosen as BEST ANSWER

    I actually solved this by looking through the documentation for react-native-reanimated. Obviously useAnimatedGestureHandler isn't deprecated because it worked with onDoubleTap, not to mention onDrag worked just fine on iOS.

    But going through the documentation on how to handle pan gestures I found this:

    const pan = Gesture.Pan()
      .onBegin(() => {
        pressed.value = true;
      })
      .onChange((event) => {
        offset.value = event.translationX;
      })
      .onFinalize(() => {
        offset.value = withSpring(0);
        pressed.value = false;
      });
    

    So instead of importing PanGestureHandler and TapGestureHandler from 'react-native-gesture-handler' and useAnimatedGestureHandler from 'react-native-reanimated' -- one only has to import Gesture and GestureDetector from 'react-native-gesture-handler'.

    Gesture ends up taking the place of useAnimatedGestureHandler while GestureDetector takes the place of components like PanGestureHandler and TapGestureHandler.

    I also ended up having to make my own contextX and contextY variables using useSharedValue() because as far as I could tell, the onBegin() and onChange() callbacks didn't have a settable context.

    Anyways, here is the fixed code that is now working perfectly on both Web and iOS:

    import { View, Image } from 'react-native';
    import Animated, {
      useAnimatedStyle,
      useSharedValue,
      withSpring,
    } from 'react-native-reanimated';
    import { Gesture, GestureDetector } from 'react-native-gesture-handler';
    
    const AnimatedImage = Animated.createAnimatedComponent(Image);
    const AnimatedView = Animated.createAnimatedComponent(View);
    
    export default function EmojiSticker({ imageSize, stickerSource }) {
      const scaleImage = useSharedValue(imageSize);
      const translateX = useSharedValue(0);
      const translateY = useSharedValue(0);
      const contextX = useSharedValue(0);
      const contextY = useSharedValue(0);
    
      const onDoubleTap = Gesture.Tap().numberOfTaps(2)
        .onEnd(() => {
          if (scaleImage.value !== imageSize * 2) {
            scaleImage.value = scaleImage.value * 2;
          } else {
            scaleImage.value = scaleImage.value / 2;
          }
        });
      const onDrag = Gesture.Pan()
        .onBegin(() => {
          contextX.value = translateX.value;
          contextY.value = translateY.value;
        })
        .onChange((event) => {
          translateX.value = event.translationX + contextX.value;
          translateY.value = event.translationY + contextY.value;
        });
    
      const imageStyle = useAnimatedStyle(() => {
        return {
          width: withSpring(scaleImage.value),
          height: withSpring(scaleImage.value),
        };
      });
      const containerStyle = useAnimatedStyle(() => {
        return {
          transform: [
            {
              translateX: translateX.value,
            },
            {
              translateY: translateY.value,
            },
          ],
        };
      });
    
      return (
        <GestureDetector gesture={onDrag}>
          <AnimatedView style={[containerStyle, { top: -350 }]}>
            <GestureDetector gesture={onDoubleTap}>
              <AnimatedImage
                source={stickerSource}
                resizeMode="contain"
                style={[imageStyle, { width: imageSize, height: imageSize }]}
              />
            </GestureDetector>
          </AnimatedView>
        </GestureDetector>
      );
    }
    
    

  2. If you are using version 2 of React Native Gesture Handler, the new way to archive the onDrag effect could be achieved with a code like:

    import { View, Image } from 'react-native';
    import { Gesture, GestureDetector } from 'react-native-gesture-handler';
    import Animated, {
        useAnimatedStyle,
        useSharedValue,
    } from 'react-native-reanimated';
    
    const AnimatedView = Animated.createAnimatedComponent(View);
    
    export default function EmojiSticker({ imageSize, stickerSource }) {
    
        const translateX = useSharedValue(0);
        const translateY = useSharedValue(0);
        const previousX = useSharedValue(0);
        const previousY = useSharedValue(0);
    
    
        const containerStyle = useAnimatedStyle(() => {
            return {
                transform: [
                    {
                        translateX: translateX.value,
                    },
                    {
                        translateY: translateY.value,
                    },
                ],
            };
        });
    
    
        const panGesture = Gesture.Pan()
            .onEnd((e) => {
                previousX.value = previousX.value + e.translationX;
                previousY.value = previousY.value + e.translationY;
            })
            .onUpdate((e) => {
                translateX.value = e.translationX + previousX.value;
                translateY.value = e.translationY + previousY.value;
            });
    
        return (
            <GestureDetector gesture={panGesture}>
                <AnimatedView style={[containerStyle]}>
                        <Image
                            source={stickerSource}
                            style={[{ width: imageSize, height: imageSize }]}
                        />
                </AnimatedView>
            </GestureDetector>
        );
    }
    
    

    More or less, the idea is to animate the translation changing it when you detect the gesture. For it, you surround the element with a gesture detector and pass to it a pan configuration. In the pan configuration you save where the item ended (to accommodate for it the next time it pans) and in onUpdate you update it to show where it is currently dragged.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search