question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

pinch and zoom image position

See original GitHub issue

Hey thanks for this nice library!

Im trying to make a instagram pinch and zoom image, I have a working zoomable and movable image, but i struggle with the release phase, when i just pinch on release the image is way of the position so it “jumps” at start.

onGestureMove works it updates the position correct, but then when i do onGestureRelease the animated value of gesturePosition.x and gesturePosition.y is wrong.

Would love some help on this 💯

Element.js

import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import { Animated, Easing } from 'react-native'
import { PanGestureHandler, PinchGestureHandler, State } from 'react-native-gesture-handler'

const ANIMATION_DURATION = 200

export default class Element extends PureComponent {
  opacity = new Animated.Value(1)

  static propTypes = {
    children: PropTypes.element.isRequired,
  }

  static contextTypes = {
    scaleValue: PropTypes.object,
    onGestureStart: PropTypes.func,
    onGestureRelease: PropTypes.func,
    gesturePosition: PropTypes.object,
  }

  onPanStateChange = ({ nativeEvent }) => {
    switch (nativeEvent.state) {
      case State.BEGAN:
        return this.onGestureStart()
      case State.END:
      case State.FAILED:
      case State.UNDETERMINED:
      case State.CANCELLED:
        return this.onGestureRelease()
      default:
        return null
    }
  }

  onGestureStart = async () => {
    const { onGestureStart, gesturePosition } = this.context

    const measurement = await this.measureSelected()
    this.measurement = measurement

    onGestureStart({ element: this, measurement })

    gesturePosition.setValue({ x: 0, y: 0 })

    gesturePosition.setOffset({
      x: measurement.x,
      y: measurement.y,
    })

    Animated.timing(this.opacity, {
      toValue: 0,
      duration: ANIMATION_DURATION,
    }).start()
  }

  onGestureRelease() {
    const { gesturePosition, scaleValue, onGestureRelease } = this.context

    Animated.parallel([
      Animated.timing(gesturePosition.x, {
        toValue: 0,
        duration: ANIMATION_DURATION,
        easing: Easing.ease,
        useNativeDriver: true,
      }),
      Animated.timing(gesturePosition.y, {
        toValue: 0,
        duration: ANIMATION_DURATION,
        easing: Easing.ease,
        useNativeDriver: true,
      }),
      Animated.timing(scaleValue, {
        toValue: 1,
        duration: ANIMATION_DURATION,
        easing: Easing.ease,
        useNativeDriver: true,
      }),
    ]).start(() => {
      gesturePosition.setOffset({
        x: this.measurement.x,
        y: this.measurement.y,
      })

      // Reset original component opacity
      this.opacity.setValue(1)

      // Reset scale value
      scaleValue.setValue(1)

      requestAnimationFrame(() => {
        onGestureRelease()
      })
    })
  }

  onGestureMove = ({ nativeEvent }) => {
    const { gesturePosition } = this.context
    const { translationX, translationY } = nativeEvent

    gesturePosition.setValue({
      x: translationX,
      y: translationY,
    })
  }

  onGesturePinch = ({ nativeEvent }) => {
    const { scaleValue } = this.context
    scaleValue.setValue(nativeEvent.scale)
  }

  setRef = el => {
    this.parent = el
  }

  /* eslint-disable no-underscore-dangle */
  measureSelected = async () => {
    const parentMeasurement = await new Promise((resolve, reject) => {
      try {
        this.parent._component.measureInWindow((x, y) => {
          resolve({ x, y })
        })
      } catch (err) {
        reject(err)
      }
    })

    return {
      x: parentMeasurement.x,
      y: parentMeasurement.y,
    }
  }

  render() {
    const imagePan = React.createRef()

    return (
      <PanGestureHandler
        onGestureEvent={this.onGestureMove}
        onHandlerStateChange={this.onPanStateChange}
        ref={imagePan}
        minPointers={2}
        maxPointers={2}
        minDist={0}
        minDeltaX={0}
        avgTouches
      >
        <PinchGestureHandler simultaneousHandlers={imagePan} onGestureEvent={this.onGesturePinch}>
          <Animated.View ref={this.setRef} style={{ opacity: this.opacity }}>
            {this.props.children}
          </Animated.View>
        </PinchGestureHandler>
      </PanGestureHandler>
    )
  }
}

Selected.js

import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import { Animated, View } from 'react-native'

const styles = {
  container: {
    flex: 1,
    position: 'absolute',
    top: 0,
    left: 0,
    bottom: 0,
    right: 0,
  },
  background: {
    position: 'absolute',
    top: 0,
    left: 0,
    bottom: 0,
    right: 0,
    backgroundColor: 'black',
  },
}

const MINIMUM_SCALE = 1
const MAXIMUM_SCALE = 5
const SCALE_MULTIPLIER = 1.2

export default class Selected extends PureComponent {
  static propTypes = {
    selected: PropTypes.object,
  }

  static contextTypes = {
    gesturePosition: PropTypes.object,
    scaleValue: PropTypes.object,
  }

  render() {
    const { selected } = this.props
    const { gesturePosition, scaleValue } = this.context

    const scale = scaleValue.interpolate({
      inputRange: [MINIMUM_SCALE, MAXIMUM_SCALE],
      outputRange: [MINIMUM_SCALE, MAXIMUM_SCALE * SCALE_MULTIPLIER],
      extrapolate: 'clamp',
    })

    const backgroundOpacityValue = scaleValue.interpolate({
      inputRange: [1, 1.2, 3],
      outputRange: [0, 0.5, 0.8],
    })

    const transform = [...gesturePosition.getTranslateTransform(), { scale }]

    // TODO: See if cloneElement glitches
    return (
      <View style={styles.container}>
        <Animated.View style={[styles.background, { opacity: backgroundOpacityValue }]} />
        <Animated.View
          style={{
            position: 'absolute',
            zIndex: 10,
            transform,
          }}
        >
          {React.cloneElement(selected.element.props.children, { disableAnimation: true })}
        </Animated.View>
      </View>
    )
  }
}

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:8 (2 by maintainers)

github_iconTop GitHub Comments

1reaction
pontusabcommented, Nov 11, 2018

Yep, @tomasgcs idea worked! Thanks

1reaction
tomasgcscommented, Sep 3, 2018

Hello this happened to me also so what I did is I don’t use setOffset Offset somehow doesn’t work right when used togehter with Animated.timing so what I did I defined a separate Animated.Value to be used as gestureOffset which is then added to the gesturePosition using Animated.add For example:

const transform = [{ translateX: Animated.add(gesturePosition.x, gestureOffset.x), }, { translateY: Animated.add(gesturePosition.y, gestureOffset.y), }, { scale }]

This workaround works, but I think it is worth looking at Native code to see where is the issue. Hope this will help.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Pinch Zoom, Pan Image and Double Tap to ... - Medium
This story shows how to zoom an image in SwiftUI using pinch gesture and tap gesture. We are using UIPinchGestureRecognizer for pinch to...
Read more >
pinch and zoom image position · Issue #244
Im trying to make a instagram pinch and zoom image, I have a working zoomable and movable image, but i struggle with the...
Read more >
Adding pinch to zoom support to image views
In this tutorial, we'll see how it's possible to use a UIScrollView to make any image view support pinch to zoom.
Read more >
Flutter Zoom Image | Pinch To Zoom - YouTube
How to zoom images in Flutter and how to create pinch to zoom in Flutter.Click here to Subscribe to Johannes Milke: ...
Read more >
Recognize a pinch gesture - .NET MAUI
This article explains how to use the pinch gesture to perform interactive zoom of an image in .NET MAUI, at the pinch location....
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found