Declarative future of gestures and animations in React Native

Declarative future of gestures and animations in React Native

Introducing react-native-reanimated.

Animations and touch related interactions are the secret ingredients that can make a good app an awesome app. Apparently, they are also the elements most sensitive to frame drops, which degrades the great experience they ought to provide. We will learn what the challenges are when building performant and responsive UI on mobile, and get to know how declarative APIs let us deal with these problems.

C659d8ee2617816280c7c7d5819f83d6?s=128

Krzysztof Magiera

May 17, 2018
Tweet

Transcript

  1. Declarative future of gestures and animations in React Native Krzysztof

    Magiera (@kzzzf)
  2. Animations hold

  3. Animations skip

  4. Animations t VSync

  5. Animations t VSync ?

  6. Animations UI thread JS thread requestAnimationFrame clb

  7. Animations UI thread JS queue background thread readFromStorage HTTPResponse

  8. Animations UI thread JS queue background thread readFromStorage HTTPResponse

  9. Perf monitor

  10. Animated state = { fade: new Animated.Value(0) }; 1. Create

    value <Animated.View style={{ opacity: this.state.fade }} /> 2. Hook it up as a view attribute Animated.timing( this.state.fade, { toValue: 1, } ).start(); 3. Animate value 4. Or attach it to an event 5. Combine/process values const fade = Animated.multiply( this.state.factor, this.state.scrollY.interpolate({ inputRange: [0, 300], outputRange: [1, 0] }) ); <Animated.ScrollView onScroll={Animated.event([ { // event.nativeEvent.contentOffset.y nativeEvent: { contentOffset: { y: this.state.scrollY } } } ])} >
  11. useNativeDriver Animated T-minus 2 years

  12. What about gestures?

  13. JS responder system ! <TouchableHighlight onPress={this.rowPressed}> <View style={styles.row}> <Text>Show this

    slide</Text> <TouchableOpacity onPress={this.checkboxPressed} disabled={false} style={styles.checkbox} > <Checkbox checked={this.state.checked} /> </TouchableOpacity> </View> </TouchableHighlight> Show this slide
  14. JS responder system ! <TouchableHighlight onPress={this.rowPressed}> <View style={styles.row}> <Text>Show this

    slide</Text> <TouchableOpacity onPress={this.checkboxPressed} disabled={true} style={styles.checkbox} > <Checkbox checked={this.state.checked} /> </TouchableOpacity> </View> </TouchableHighlight> Show this slide
  15. <View/> <Touchable/> <Text/> <Touchable/> <Image/> <Checkbox/> JS responder system !

    TOUCH DOWN TOUCH MOVE TOUCH MOVE TOUCH MOVE TOUCH UP JS Thread UI Thread <Image/>
  16. <View/> <Touchable/> <Text/> <Touchable/> <Image/> <Checkbox/> JS responder system !

    TOUCH DOWN TOUCH MOVE TOUCH MOVE TOUCH MOVE TOUCH UP JS Thread UI Thread <Touchable/> <Image/> <Touchable/> BUBBLING <View/> <Checkbox/>
  17. <Animated.ScrollView onScroll={Animated.event( [ { // nativeEvent.contentOffset.y nativeEvent: { contentOffset: {

    y: this.state.scrollY } } } ], { useNativeDriver: true } )} > What about gestures? this._pan = PanResponder.create({ onMoveShouldSetPanResponder: YES, onMoveShouldSetPanResponderCapture: YES, onPanResponderMove: Animated.event( [ null, { dx: this._translateValue } ], { useNativeDriver: false } ) }); DIRECT BUBBLING
  18. What about gestures?

  19. react-native-gesture-handler 1.0.0 is out! " react-native-gesture-handler https://kmagiera.github.io/react-native-gesture-handler/

  20. react-native-gesture-handler Handler Parameters Event attributes Pan minDist, minPointers translationXY, absoluteXY

    Pinch scale, focalXY, velocity Tap numberOfTaps, maxDuration numberOfPointers Rotation angle, anchorXY, velocity
  21. ‣Component for each gesture type (pan, pinch, etc) ‣Natively backed

    “touchables” ‣Declarative API for defining cross handler interactions ‣Extras: drawer, swipeable row, more to come… react-native-gesture-handler
  22. react-native-gesture-handler <RotationGestureHandler onGestureEvent={Animated.event( [{ nativeEvent: { rotation: this.rotationValue } }],

    { useNativeDriver: true } )} > <Animated.View style={animatedTransforms} /> </RotationGestureHandler>; DIRECT
  23. class Snappable extends Component { translateX = new Animated.Value(0); render()

    { return ( <PanGestureHandler> <Animated.View style={[ styles.box, { transform: [{ translateX: this.translateX }] } ]} /> </PanGestureHandler> ); } }
  24. class Snappable extends Component { translateX = new Animated.Value(0); render()

    { return ( <PanGestureHandler onGestureEvent={Animated.event( [{ nativeEvent: { translationX: this.translateX } }], { useNativeDriver: true } )}> <Animated.View style={[ styles.box, { transform: [{ translateX: this.translateX }] } ]} /> </PanGestureHandler> ); } }
  25. class Snappable extends Component { translateX = new Animated.Value(0); render()

    { return ( <PanGestureHandler onGestureEvent={Animated.event( [{ nativeEvent: { translationX: this.translateX } }], { useNativeDriver: true } )} onHandlerStateChange={event N> { if (event.nativeEvent.oldState === State.ACTIVE) { // Check if the last state was "ACTIVE" Animated.spring(this.translateX, { toValue: 0, velocity: event.nativeEvent.velocityX, useNativeDriver: true, }).start(); } }}> <Animated.View style={[ styles.box, { transform: [{ translateX: this.translateX }] } ]} /> </PanGestureHandler> ); } }
  26. DECLARATIVE ALL THE THINGS!!!

  27. Side effects

  28. IF-ELSE node

  29. ImagePreview component challenge ‣ Correctly centred pinch ‣ Pan with

    inertia ‣ Pan friction on edges ‣ Bouncing from edges ‣ Pinch friction ‣ Bouncing zoom ‣ Pinch&Pan simultaneously
  30. ImagePreview component challenge IMPE RATIVE FREE IMPE RATIVE FREE

  31. github.com/kmagiera/react-native-reanimated

  32. { // Animated this.transX = cond( eq(state, State.ACTIVE), [ stopClock(clock),

    set(transX, add(transX, sub(dragX, prevDragX))), set(prevDragX, dragX), transX, ], [ set(prevDragX, 0), set( transX, cond(defined(transX), runSpring(clock, transX, dragVX), 0) ), ] ); }
  33. Clocks export default function decay(clock, state, config) { const lastTime

    = cond(state.time, state.time, clock); const deltaTime = sub(clock, lastTime); // v0 = v / 1000 // v = v0 * powf(deceleration, dt); // v = v * 1000; // x0 = x; // x = x0 + v0 * deceleration * (1 - powf(deceleration, dt)) / (1 - deceleration) const kv = pow(config.deceleration, deltaTime); const kx = divide( multiply(config.deceleration, sub(1, kv)), sub(1, config.deceleration) ); const v0 = divide(state.velocity, 1000); const v = multiply(v0, kv, 1000); const x = add(state.position, multiply(v0, kx)); return [ set(state.position, x), set(state.velocity, v), set(state.time, clock), cond(lessThan(abs(v), VELOCITY_EPS), set(state.finished, 1)), ]; }
  34. Lower level abstraction export const diffClamp = function(a, minVal, maxVal)

    { const value = new AnimatedValue(); return set( value, min( max( add(cond(defined(value), value, a), diff(a)), minVal ), maxVal ) ); };
  35. ‣ More generic primitive node types ‣ Can be used

    to implement Animated compatible API w/o writing specific native code for things like: ‣ Complex nodes such as “diffClamp” ‣ Interactions such as animated value tracking or animation staggering ‣ Conditional evaluation & nodes with side effects ‣ Less native code & more cross platform JS code ‣ No more “useNativeDriver” – all animations runs on UI thread by default Reanimated highlights
  36. Thank you! Krzysztof Magiera krzys@swmansion.com @kzzzf