Animations

Evil Bridges and Blocked Threads

Thupi Ceylons

Software Engineer at Danske Bank

I <3 awesome software!

Fast

Smooth

Aesthetic 

Fast

Smooth

Aesthetic 

Thats why we use React Native! 

Why use React Native?

Cross Platform

Web Technology (Javascript)

ReactJS like

What sucks about React Native?

Javascript Runtime

Layouting Overhead

Data Serialization

Performance

This is really the reason im giving this talk

So why is React Native slow

The Evil Async Bridge

Bridge

Async is as virus

60 FPS

Declarative

declarative programming focuses on what the program should accomplish.

 

Benefits:
Easy to Optimize
 

Imperative

Imperative programming focuses on describing how a program operates.

 

 

Benefits:
Easy to describe complex operations.

import { Animated } from 'react-native';

Let's take a deeper look

import React from "react";
import { Animated, TouchableOpacity, Text } from "react-native";

export default class AnimatedBox extends React.Component {
  animatedValue = new Animated.Value(0);

  handlePress = () => {
    Animated.spring(this.animatedValue, {
      toValue: 100,
      tension: 30,
      friction: 12,
      useNativeDriver: true
    }).start();
  };

  render() {
    const style = {
      height: 100,
      width: 100,
      backgroundColor: "red"
    };
    const animatedStyle = {
      transform: [{ translateX: this.animatedValue }]
    };

    return (
      <Animated.View style={animatedStyle}>
        <TouchableOpacity style={style} onPress={this.handlePress}>
          <Text>Press here</Text>
        </TouchableOpacity>
      </Animated.View>
    );
  }
}
import React from "react";
import { Animated, View, PanResponder } from "react-native";

export default class AnimatedBox extends React.Component {
  translateX = new Animated.Value(1);
  translateXValue = 0;
  translateY = new Animated.Value(1);
  translateYValue = 0;

  responder = PanResponder.create({
    ...
  });

  componentDidMount() {
    this.translateY.addListener(({ value }) => {
      this.translateYValue = value;
    });

    this.translateX.addListener(({ value }) => {
      this.translateXValue = value;
    });
  }

  render() {
    const style = {
      ...
    };
    return <Animated.View style={style} {...this.responder.panHandlers} />;
  }
}
export default class AnimatedBox extends React.Component {
  translateX = new Animated.Value(1);
  translateXValue = 0;
  translateY = new Animated.Value(1);
  translateYValue = 0;

    responder = PanResponder.create({
    onMoveShouldSetPanResponder: (e, gestureState) => true,
    onPanResponderGrant: () => {
      this.translateY.setOffset(this.translateYValue);
      this.translateY.setValue(0);
      this.translateX.setOffset(this.translateXValue);
      this.translateX.setValue(0);
    },
    onPanResponderMove: (e, gestureState) => {
      this.translateY.setValue(gestureState.dy);
      this.translateX.setValue(gestureState.dx);
    },
    onPanResponderRelease: (e, gestureState) => {
      this.translateY.flattenOffset();
      this.translateX.flattenOffset();
    }
  });

  componentDidMount() {
    this.translateY.addListener(({ value }) => {
      this.translateYValue = value;
    });

    this.translateX.addListener(({ value }) => {
      this.translateXValue = value;
    });
  }

  ...

}

The problem is Async

The JS thread might be blocked

Just use useNativeDriver!

Not really, PanResponder is written in JS

The main takeaway

Do not cross the evil bridge unless necessary!

So what can we do...

react-native-gesture-handler

by Krzysztof Magiera​

import {
  Animated
} from "react-native";

import { PanGestureHandler, State } from "react-native-gesture-handler";

export class Swipeable extends Component {

    // ...

  _onHandlerStateChange = ({ nativeEvent }) => {
    if (nativeEvent.oldState === State.ACTIVE) {
        // ...
    }
  };

  render() {
    
    // ...

    return (
      <View style={[styles.root, style]} onLayout={this.handleLayout}>
        <PanGestureHandler
          ref={this.swipeable}
          shouldCancelWhenOutside={false}
          onGestureEvent={this._onGestureEvent}
          onHandlerStateChange={this._onHandlerStateChange}
        >

          {/* ... */}

        </PanGestureHandler>
      </View>
    );
  }
}
import { Animated, PanResponder } from "react-native";

export class Swipeable extends Component {

  // ...

  panResponder = PanResponder.create({
    onMoveShouldSetPanResponderCapture: (evt, gestureState) => {
      const dx = Math.abs(gestureState.dx);
      const dy = Math.abs(gestureState.dy);
      return dx > dy && dx > X_AXIS_CAPTURE_THRESHOLD;
    },
    onMoveShouldSetPanResponder: () => false,
    onPanResponderTerminationRequest: () => false,
    onPanResponderRelease: this.handleTouchEnd,
    onPanResponderTerminate: this.handleTouchEnd,
    onPanResponderMove: this.handleTouchMove,
    onPanResponderGrant: this.handleTouchStart
  });

  handleTouchStart = (event, gestureState) => { ... };

  handleTouchMove = (event, gestureState) => { ... };

  handleTouchEnd = (event, gestureState) => { ... };

  render() {

    // ...

    return (
      <View style={[styles.root, style]} onLayout={this.handleLayout}>
        <Animated.View
          {...this.panResponder.panHandlers}
          style={[styles.container, containerStyle, sceneContainerStyle]}
        >
          {/* ... */}
        </Animated.View>
      </View>
    );
  }
}

Original Swipeable

New Swipeable

Original Swipeable

New Swipeable

Features that cannot be achived by Animated

render() {
  return (
    <PanGestureHandler
      activeOffsetY={-20}
      onGestureEvent={this.handelScaleGesture}
    >
      <PanGestureHandler
        activeOffsetY={20}
        onGestureEvent={this.handelTranslateGesture}
      >
        <View style={{
          height: 150,
          justifyContent: 'center',
        }}>
         {/* ... */}
        </View>
      </PanGestureHandler>
    </PanGestureHandler>
  );
}

Bonus

react-native-reanimated

by Krzysztof Magiera​

return (
  <PanGestureHandler
    onGestureEvent={event([
      {
        nativeEvent: ({ translationX: x, translationY: y, state }) => block([
          set(this._transX, add(x, offsetX)),
          set(this._transY, add(y, offsetY)),
          cond(
            eq(state, State.END),
            [
              set(this.offsetX, add(this.offsetX, x)),
              set(this.offsetY, add(this.offsetY, y))
            ]
          )
        ])
      },
    ])}
  >
    <Animated.View
      style={{
        transform: [{
          translateX: this._transX, translateY: this._transY }]
        }
      }
    />
  </PanGestureHandler>
);

Declarative logic in Animations

Thank you :-) ! 

Catch me at: THCE@danskebank.dk

Source code: https://github.com/thupi/db-rn-animations

Animations, Evil Bridges and Blocked Threads

By thupi

Animations, Evil Bridges and Blocked Threads

  • 387