javascriptcssreactjsreact-native

how to place the co-ordinates of View to the absolute View in react native


I'm trying to create shared-element transition. Using ref i'm accessing the View location (height, width, pageX, pageY) from measure method and i have another View with absolute position where i'm passing this data so that transition start from that place

but instead after adding the pageX and pageY to respective left and top - the absolute view is not on the exact location

import { Image } from "expo-image";
import React, { useRef } from "react";
import { Button, StyleSheet, Text, View } from "react-native";
import Animated, {
    useAnimatedStyle,
    useSharedValue,
} from "react-native-reanimated";

const AnimatedExpoImage = Animated.createAnimatedComponent(Image);

const Example = () => {
    const imageViewRef = useRef<View>(null);
    const rootViewRef = useRef<View>(null);

    const position = useSharedValue({ x: 0, y: 0 });
    const size = useSharedValue({ width: 0, height: 0 });

    const activeImagePositionStyle = useAnimatedStyle(() => {
        return {
            top: position.value.y,
            left: position.value.x,
        };
    });

    const activeImageSizeStyle = useAnimatedStyle(() => {
        return {
            height: size.value.height,
            width: size.value.width,
        };
    });

    return (
        <View ref={rootViewRef} style={{ flex: 1, backgroundColor: "white" }}>
            <View>
                <View
                    style={{
                        justifyContent: "center",
                        alignItems: "center",
                        height: 50,
                    }}
                >
                    <Text>Paul Jarvis</Text>
                </View>
                <View
                    ref={imageViewRef}
                    style={{
                        height: 200,
                        width: "85%",
                        alignSelf: "center",
                    }}
                >
                    <Image
                        contentFit="cover"
                        style={{ height: "100%", width: "100%" }}
                        source={{
                            uri: "https://picsum.photos/id/16/2500/1667",
                        }}
                    />
                </View>
                <Button
                    title="Get Details"
                    onPress={() => {
                        imageViewRef.current?.measure(
                            (x, y, width, height, pageX, pageY) => {
                                position.value = { x: pageX, y: pageY };
                                size.value = { width, height };
                            }
                        );
                    }}
                />
            </View>

            <View style={[StyleSheet.absoluteFill]}>
                <View style={{ flex: 1 }}>
                    <AnimatedExpoImage
                        contentFit="cover"
                        style={[
                            styles.image,
                            activeImagePositionStyle,
                            activeImageSizeStyle,
                        ]}
                        source={{
                            uri: "https://picsum.photos/id/17/2500/1667",
                        }}
                    />
                </View>
            </View>
        </View>
    );
};

export default Example;

const styles = StyleSheet.create({
    image: {
        width: null,
        height: null,
        position: "absolute",
        top: 0,
        left: 0,
    },
});

GIF error example


Solution

  • The issue comes from how measure works. When you call measure, the values (pageX, pageY) are returned in absolute screen coordinates.

    But when you position a view with position: "absolute", the coordinates need to be relative to the parent container (in your case, rootViewRef). That’s why simply setting top: pageY and left: pageX doesn’t align the view correctly.

    The solution is to either convert those coordinates to be relative, or use measureLayout, which directly gives you coordinates relative to a specific parent:

    imageViewRef.current?.measureLayout(
      rootViewRef.current,
      (x, y, width, height) => {
        position.value = { x, y };
        size.value = { width, height };
      }
    );
    

    This way, x and y are already relative to the container where your absolutely positioned view lives, so it lines up perfectly.