jskidd3
jskidd3

Reputation: 4783

How to rotate an image using the shortest route in React Native

I'm trying to rotate an image using React Native. I have managed to do this as shown in the example below, but I would prefer that the arrow spins to the new bearing using the shorter route (i.e. clockwise in this case instead of counterclockwise).

If the current bearingAnim value is 315 and the next value is 45 the rotation will transition backwards, rather than going from 315 => 45.

How can I adjust the code to use the shortest rotation?

const bearing = this.bearingAnim.interpolate({
  inputRange: [0, 360],
  outputRange: ['0deg', '360deg'],
});

return <Animated.Image style={{ transform: [{ rotate: bearing }] }} source={markerGreen} />;

enter image description here

Upvotes: 1

Views: 1271

Answers (2)

Yevhen Horbunkov
Yevhen Horbunkov

Reputation: 15530

As each direction may be set as a positive or negative angle (clockwise/counter clockwise rotation), e.g. 315 (clockwise) is -45 (counter clockwise), you may compare the absolute values of a delta between current direction and new direction (both clockwise and counter clockwise) and set your new angle to the one having least absolute value of the delta:

const getNewDirectionangle = (direction, newDirection) => {
          const delta = {
            clockwise: (newDirection-direction)%360,
            counterClockwise: (newDirection-direction-360)%360
          }
          return
              Math.abs(delta.clockwise) < Math.abs(delta.counterClockwise) ?
              direction + delta.clockwise :
              direction + delta.counterClockwise
          
      }

Following is a React.js live-demo to illustrate the concept:

const { useState } = React,
      { render } = ReactDOM,
      rootNode = document.getElementById('root')
      
const Pointer = ({rotationAngle}) => {

  return (
    <div className="map">
      <div 
        className="pointer"
        style={{transform: `rotate(${rotationAngle}deg)`}}
      />
    </div>
  )
}

const Controls = ({onRotate}) => {
  const [azimuth, setAzimuth] = useState(),
        onInput = ({target:{value}}) => setAzimuth(value%360)
  return (
    <form>
      <input type="number" onChange={onInput} />
      <input type="button" value="Rotate" onClick={() => onRotate(azimuth)} />
    </form>
  )
}

const App = () => {
  const [direction, setDirection] = useState(0),
        onRotate = newDirection => {
          const delta = {
            clockwise: (newDirection-direction)%360,
            counterClockwise: (newDirection-direction-360)%360
          }
          setDirection(
              Math.abs(delta.clockwise) < Math.abs(delta.counterClockwise) ?
              direction + delta.clockwise :
              direction + delta.counterClockwise
          )
        }
  return (
    <div className="wrapper">
      <Pointer rotationAngle={direction} />
      <Controls {...{onRotate}}/>
    </div>
  )
}

render (
  <App />,
  rootNode
)
.wrapper {
  width: 250px;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.map {
  width: 100%;
  height: 120px;
  display: flex;
  flex-direction: row;
  justify-content: center;
  background-image: url("https://i.sstatic.net/3yuKI.png");
  background-size: cover;
  margin: 5px
}

.pointer {
  transition: transform 1s ease-in;
  transform-origin: 50% 58%;
  background-image: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMS43NTIgMjYuMjU2Ij48ZGVmcyBpZD0iZGVmczUiPjxjbGlwUGF0aCBpZD0iYSIgY2xpcFBhdGhVbml0cz0idXNlclNwYWNlT25Vc2UiPjxwYXRoIGQ9Ik0xNi4zMy0uNDhoODMuNzM0djEwOS4zNUgxNi4zM3oiIGlkPSJwYXRoMiIgLz48L2NsaXBQYXRoPjwvZGVmcz48cGF0aCBkPSJNMjguOTk5IDg3LjMzNmMtMTYuMDUtMTYuMDUtMTYuMDUtNDIuMDg3IDAtNTguMTM3QzM4LjY4NSAxOS41MTIgNDguMzcgOS44MjYgNTguMDc3LjE0TDg3LjEzNiAyOS4yYzE2LjA1IDE2LjA1IDE2LjA1IDQyLjA4NyAwIDU4LjEzNy0xNi4wNSAxNi4wNS00Mi4wODcgMTYuMDUtNTguMTM3IDB6IiBjbGlwLXBhdGg9InVybCgjYSkiIGZpbGw9IiM3MGFkNDciIGZpbGwtcnVsZT0iZXZlbm9kZCIgdHJhbnNmb3JtPSJtYXRyaXgoLjI2NDU4IDAgMCAuMjY0NTggLTQuNDg4IC0uMDM3KSIgaWQ9InBhdGg3IiAvPjxwYXRoIGQ9Ik0zLjA3NCAxNS4zODJjMC00LjI4OSAzLjQ5NS03Ljc3MyA3Ljc5NC03Ljc3MyA0LjMwNSAwIDcuNzk0IDMuNDg0IDcuNzk0IDcuNzczIDAgNC4yOTQtMy40ODkgNy43NzMtNy43OTQgNy43NzMtNC4zIDAtNy43OTQtMy40NzktNy43OTQtNy43NzN6IiBmaWxsPSIjZmZmIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGlkPSJwYXRoOSIgLz48L3N2Zz4=");
  background-repeat: no-repeat;
  background-size: cover;
  width: 50px;
  height: 60px;
  margin: 20px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>

Upvotes: 1

Yoel
Yoel

Reputation: 7965

I wrote you a function that calculates the correct angle according to the old direction

She needs to get the old and new angle It will return the direction you need to add to reach the new direction (If the arrow needs to move back it will return minus and it will move back)


function newRotate(oldV, newV) {
  const diff1 = newV - oldV;
  let myOldV = oldV;
  if (newV < oldV) newV += 360;
  else myOldV += 360;

  const diff2 = newV - myOldV;
  const shortDiff = Math.abs(diff1) < Math.abs(diff2) ? diff1 : diff2;
  return oldV + shortDiff;
}

Here's how to use it

const value =newRotate(oldValue,newValue)

 Animated.timing(routeAnimtion, {
      toValue: value,
      duration: 10,
    }).start();

Upvotes: 1

Related Questions