Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does 'offset' exist in React Native Panresponder?

TL;DR: I am using the Panresponder code from the React Native docs, and need help understanding why the 'offset' value is used, as opposed to just using the animated value.

Full Question:

The Scenario:

I am using a Panresponder in React Native to drag and drop objects around the screen. I am using standard code from the RN docs.

Basically, the draggable object has an animated position value. When you click the object, the offset on that animated value is set to the animated value, and the animated value is set to zero. As you drag, the animated value is incrementally set to the magnitude of how far it has been dragged in that gesture. When you release the object, the offset is added to the animated value, and the offset is then set to zero.

Example:

For example, if the object starts from position 0, then initially both the animated value and the offset are set to 0. If you drag the object by 100px, the animated value gradually increases from 0 to 100 as you drag. When you release, the zero offset is added to the animated value (so nothing happens). If you click the object again, the offset is set to 100, and the animated value is re-set to 0. If you drag the object another 50px, the animated value increases from 0 to 50. When you release the object, the 100 offset is added to the animated value, which becomes 150, and the offset is re-set to zero.

In this way, the animated value always holds the distance dragged in the current gesture, with the offset saving the position that the object was at before the current drag gesture started, and when you release the object, that saved offset value is tacked onto the animated value, so that when the object is at rest, the animated value contains the total distance that the object has been dragged by all gestures combined.

Code:

Here's the code I'm using to do this:

this.animatedValue.addListener((value) => this._value = value); // Make this._value hold the value of this.animatedValue (essentially extract the x and y values from the more complex animatedValue)
this.panResponder = PanResponder.create({
    onPanResponderGrant: () => { // When user clicks to initiate drag
        this.animatedValue.setOffset({ // Save 'distance dragged so far' in offset
            x: this._value.x,
            y: this._value.y,
        })
        this.animatedValue.setValue({ x: 0, y: 0}) // Set this.animatedValue to (0, 0) so that it will hold only 'distance so far in current gesture'
    },
    onPanResponderMove: Animated.event([ // As object is dragged, continually update animatedValue
        null, { dx: this.animatedValue.x, dy: this.animatedValue.y}
    ]),
    onPanResponderRelease: (e, gestureState) => { // On release, add offset to animatedValue and re-set offset to zero.
        this.animatedValue.flattenOffset();
    }
}

My Question:

This code works perfectly well. When I don't understand though, is why do we need the offset? Why do we need to re-set the animated value to zero on every new gesture, save its value in offset, and re-add that to the animated value after it's finished being dragged? When the object is released, it ends up just holding the total distance dragged, so why not just use the animated value and not use the offset? With my example above, why not just increment animated value to 100 when you drag it 100px, then when you click and drag it again, keep updating the animated value?

Possible Solution:

The only advantage I can think of to using the offset is that animatedValue will now allow you to keep track of the 'distance so far in current gesture', as opposed to just 'total distance so far over all gestures combined'. There might be a scenario in which you need the 'distance so far in current gesture' value, so I'm wondering if this is the only reason to ever use the offset, or is there a more fundamental reason I'm missing why we should use it all the time?

Any insight would be great.

Thanks!

like image 810
gkeenley Avatar asked Oct 11 '25 11:10

gkeenley


1 Answers

Actually the logic isn't right in the example you used because it's just a partial example using flattenOffset that isn't meant to be used for standard drag/drop behaviour (see the bottom paragraph: https://animationbook.codedaily.io/flatten-offset/):

Because we reset our offset and our animated value in the onPanResponderGrant, the call to flattenOffset is unnecessary, here. However, in the case where we want to trigger an animation from the released location to another location, flattenOffset is required.

The whole point of the offset is that you don't need to keep track of the absolute position value in a separate variable. So you were right to doubt the need for the offset given that you where storing the absolute position in this._value.

At the beginning of a drag, the x/y values of the Animated.Value start from [0, 0], so the drag is relative to the starting position:

  • offset + [0, 0] = absolute position at the beginning of a drag
  • offset + [x, y] = absolute position at the end of the drag

For the next drag to start at the right position, you just need to add [x, y] to the offset, which is done by extractOffset():

this.panResponder = PanResponder.create({
    // Allow dragging
    onStartShouldSetPanResponder: (e, gesture) => true,
    // Update position on move
    onPanResponderMove: (e, gestureState)=> {
        Animated.event([
            null,
           {dx: this.animatedValue.x, dy: this.animatedValue.y},
        ])(e, gestureState)
    },
    // Update offset once we're done moving
    onPanResponderRelease: (e, gestureState)=> {
        this.animatedValue.extractOffset();
    }
});

Thanks to the offset, you don't need this._value anymore to get the proper drag behaviour.

like image 140
Maxweel Avatar answered Oct 14 '25 03:10

Maxweel