Skip to content

Instantly share code, notes, and snippets.

@NileshPatel17
Forked from intergalacticspacehighway/PinchToZoom.tsx
Created December 30, 2021 12:03
Show Gist options
  • Save NileshPatel17/035264f4624b8875eeee979197142536 to your computer and use it in GitHub Desktop.
Save NileshPatel17/035264f4624b8875eeee979197142536 to your computer and use it in GitHub Desktop.

Revisions

  1. @intergalacticspacehighway intergalacticspacehighway revised this gist Dec 30, 2021. 1 changed file with 9 additions and 10 deletions.
    19 changes: 9 additions & 10 deletions PinchToZoom.tsx
    Original file line number Diff line number Diff line change
    @@ -28,12 +28,9 @@ export const PinchToZoom = ({ children }) => {
    const translation = { x: useSharedValue(0), y: useSharedValue(0) };
    const { onLayout, layout } = useLayout();

    const imageLeft = layout ? -layout.height / 2 : 0;
    const imageTop = layout ? -layout.width / 2 : 0;

    const handler = useAnimatedGestureHandler<PinchGestureHandlerGestureEvent>({
    onStart(e, ctx: any) {
    // On android, we get focalX and focalY always 0 in onStart callback
    // On android, we get focalX and focalY 0 in onStart callback. So, use a flag and set initial focalX and focalY in onActive
    // 😢 https://github.com/software-mansion/react-native-gesture-handler/issues/546
    ctx.start = true;
    },
    @@ -87,6 +84,9 @@ export const PinchToZoom = ({ children }) => {
    },
    });

    const imageLeftForSettingTransformOrigin = layout ? -layout.height / 2 : 0;
    const imageTopForSettingTransformOrigin = layout ? -layout.width / 2 : 0;

    const animatedStyles = useAnimatedStyle(() => {
    return {
    transform: [
    @@ -95,16 +95,16 @@ export const PinchToZoom = ({ children }) => {
    translateY: translation.y.value,
    },

    { translateX: imageLeft + origin.x.value },
    { translateY: imageTop + origin.y.value },
    { translateX: imageLeftForSettingTransformOrigin + origin.x.value },
    { translateY: imageTopForSettingTransformOrigin + origin.y.value },
    {
    scale: scale.value,
    },
    { translateX: -(imageLeft + origin.x.value) },
    { translateY: -(imageTop + origin.y.value) },
    { translateX: -(imageLeftForSettingTransformOrigin + origin.x.value) },
    { translateY: -(imageTopForSettingTransformOrigin + origin.y.value) },
    ],
    };
    }, [imageTop, imageLeft]);
    }, [imageTopForSettingTransformOrigin, imageLeftForSettingTransformOrigin]);

    const clonedChildren = useMemo(
    () =>
    @@ -122,7 +122,6 @@ export const PinchToZoom = ({ children }) => {
    };



    const Example = () => (
    <PinchToZoom>
    <Animated.Image
  2. @intergalacticspacehighway intergalacticspacehighway revised this gist Dec 30, 2021. 1 changed file with 18 additions and 9 deletions.
    27 changes: 18 additions & 9 deletions PinchToZoom.tsx
    Original file line number Diff line number Diff line change
    @@ -33,18 +33,27 @@ export const PinchToZoom = ({ children }) => {

    const handler = useAnimatedGestureHandler<PinchGestureHandlerGestureEvent>({
    onStart(e, ctx: any) {
    origin.x.value = e.focalX;
    origin.y.value = e.focalY;

    ctx.offsetFromFocalX = origin.x.value;
    ctx.offsetFromFocalY = origin.y.value;
    ctx.prevTranslateOriginX = origin.x.value;
    ctx.prevTranslateOriginY = origin.y.value;
    ctx.prevPointers = e.numberOfPointers;
    // On android, we get focalX and focalY always 0 in onStart callback
    // 😢 https://github.com/software-mansion/react-native-gesture-handler/issues/546
    ctx.start = true;
    },

    onActive(e, ctx: any) {
    if (ctx.start) {
    origin.x.value = e.focalX;
    origin.y.value = e.focalY;

    ctx.offsetFromFocalX = origin.x.value;
    ctx.offsetFromFocalY = origin.y.value;
    ctx.prevTranslateOriginX = origin.x.value;
    ctx.prevTranslateOriginY = origin.y.value;
    ctx.prevPointers = e.numberOfPointers;

    ctx.start = false;
    }

    scale.value = e.scale;

    if (ctx.prevPointers !== e.numberOfPointers) {
    ctx.offsetFromFocalX = e.focalX;
    ctx.offsetFromFocalY = e.focalY;
    @@ -95,7 +104,7 @@ export const PinchToZoom = ({ children }) => {
    { translateY: -(imageTop + origin.y.value) },
    ],
    };
    }, []);
    }, [imageTop, imageLeft]);

    const clonedChildren = useMemo(
    () =>
  3. @intergalacticspacehighway intergalacticspacehighway revised this gist Dec 30, 2021. 1 changed file with 13 additions and 0 deletions.
    13 changes: 13 additions & 0 deletions PinchToZoom.tsx
    Original file line number Diff line number Diff line change
    @@ -111,3 +111,16 @@ export const PinchToZoom = ({ children }) => {
    </PinchGestureHandler>
    );
    };



    const Example = () => (
    <PinchToZoom>
    <Animated.Image
    style={{ width: 277, height: 368 }}
    source={{
    uri: "https://images.unsplash.com/photo-1536152470836-b943b246224c?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=876&q=80",
    }}
    />
    </PinchToZoom>
    );
  4. @intergalacticspacehighway intergalacticspacehighway created this gist Dec 30, 2021.
    113 changes: 113 additions & 0 deletions PinchToZoom.tsx
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,113 @@
    import React, { useMemo, useState } from "react";
    import { LayoutChangeEvent, StyleSheet } from "react-native";
    import {
    PinchGestureHandler,
    PinchGestureHandlerGestureEvent,
    } from "react-native-gesture-handler";
    import Animated, {
    useAnimatedGestureHandler,
    useAnimatedStyle,
    useSharedValue,
    withSpring,
    } from "react-native-reanimated";

    const useLayout = () => {
    const [layout, setLayout] = useState<
    LayoutChangeEvent["nativeEvent"]["layout"] | undefined
    >();
    const onLayout = (e) => {
    setLayout(e.nativeEvent.layout);
    };

    return { onLayout, layout };
    };

    export const PinchToZoom = ({ children }) => {
    const scale = useSharedValue(1);
    const origin = { x: useSharedValue(0), y: useSharedValue(0) };
    const translation = { x: useSharedValue(0), y: useSharedValue(0) };
    const { onLayout, layout } = useLayout();

    const imageLeft = layout ? -layout.height / 2 : 0;
    const imageTop = layout ? -layout.width / 2 : 0;

    const handler = useAnimatedGestureHandler<PinchGestureHandlerGestureEvent>({
    onStart(e, ctx: any) {
    origin.x.value = e.focalX;
    origin.y.value = e.focalY;

    ctx.offsetFromFocalX = origin.x.value;
    ctx.offsetFromFocalY = origin.y.value;
    ctx.prevTranslateOriginX = origin.x.value;
    ctx.prevTranslateOriginY = origin.y.value;
    ctx.prevPointers = e.numberOfPointers;
    },

    onActive(e, ctx: any) {
    scale.value = e.scale;
    if (ctx.prevPointers !== e.numberOfPointers) {
    ctx.offsetFromFocalX = e.focalX;
    ctx.offsetFromFocalY = e.focalY;
    ctx.prevTranslateOriginX = ctx.translateOriginX;
    ctx.prevTranslateOriginY = ctx.translateOriginY;
    }

    ctx.translateOriginX =
    ctx.prevTranslateOriginX + e.focalX - ctx.offsetFromFocalX;
    ctx.translateOriginY =
    ctx.prevTranslateOriginY + e.focalY - ctx.offsetFromFocalY;

    translation.x.value = ctx.translateOriginX - origin.x.value;
    translation.y.value = ctx.translateOriginY - origin.y.value;

    ctx.prevPointers = e.numberOfPointers;
    },
    onEnd() {
    scale.value = withSpring(1, {
    stiffness: 60,
    overshootClamping: true,
    });
    translation.x.value = withSpring(0, {
    stiffness: 60,
    overshootClamping: true,
    });
    translation.y.value = withSpring(0, {
    stiffness: 60,
    overshootClamping: true,
    });
    },
    });

    const animatedStyles = useAnimatedStyle(() => {
    return {
    transform: [
    { translateX: translation.x.value },
    {
    translateY: translation.y.value,
    },

    { translateX: imageLeft + origin.x.value },
    { translateY: imageTop + origin.y.value },
    {
    scale: scale.value,
    },
    { translateX: -(imageLeft + origin.x.value) },
    { translateY: -(imageTop + origin.y.value) },
    ],
    };
    }, []);

    const clonedChildren = useMemo(
    () =>
    React.cloneElement(children, {
    style: [StyleSheet.flatten(children.props.style), animatedStyles],
    }),
    [children]
    );

    return (
    <PinchGestureHandler onGestureEvent={handler}>
    <Animated.View onLayout={onLayout}>{clonedChildren}</Animated.View>
    </PinchGestureHandler>
    );
    };