Animation in React Native

What's the problem of this demo?

React Native Render, Commit and Mount

  1. Render: React executes product logic which creates a React Element Trees in JavaScript. From this tree, the renderer creates a React Shadow Tree in C++.

  2. Commit: After a React Shadow Tree is fully created, the renderer triggers a commit. This promotes both the React Element Tree and the newly created React Shadow Tree as the “next tree” to be mounted. This also schedules calculation of its layout information.

  3. Mount: The React Shadow Tree, now with the results of layout calculation, is transformed into a Host View Tree.

Threading Model

  • UI thread (often called main): The only thread that can manipulate host views.
  • JavaScript thread: This is where React’s render phase, as well as layout, are executed.



Features:

  • thread safe and synchronous
  • immutable data structures
    • enforced by C++ “const correctness” feature
  • every update in React creates or clones new objects

Render in a JS Thread (most common scenario)

Alt text

Render in the UI Thread (high priority event on the UI Thread)

Alt text

https://reactnative.dev/architecture/threading-model

Initial Render

Alt text

function MyComponent() {
  return (
    <View>
      <Text>Hello, World</Text>
    </View>
  )
}

Alt text

  • Layout Calculation
    • yoga
    • the layout calculation of some components depend on the host platform (e.g. Text, TextInput, etc.).
  • Tree Promotion
    • New Tree → Next Tree
    • The “next tree” mounts on the next “tick” of the UI Thread

Alt text

React Shadow Tree -> Host View Tree

  • Tree Diffing and View Flattening
    • Computes the diff between the “previously rendered tree” and the “next tree” entirely in C++.
  • Tree Promotion (Next Tree → Previously Rendered Tree)
  • View Mounting. This step executes in the host platform on UI thread.

React State Updates

function MyComponent() {
  return (
    <View>
      <View style={{ backgroundColor: 'red', height: 20, width: 20 }} />
      <View style={{ backgroundColor: 'blue', height: 20, width: 20 }} />
    </View>
  )
}

Assume that as the result of a state update in JavaScript product logic, the background of the first nested changes from 'red' to 'yellow'. This is what the new React Element Tree might look:

<View>
  <View style={{ backgroundColor: 'yellow', height: 20, width: 20 }} />
  <View style={{ backgroundColor: 'blue', height: 20, width: 20 }} />
</View>

Let’s explore each phase of the render pipeline during a state update.

Alt text

Alt text

Let's assign T to the “previously rendered tree” and T' to the “new tree”:

  • CloneNode(Node 3, {backgroundColor: 'yellow'}) → Node 3'
  • CloneNode(Node 2) → Node 2'
  • AppendChild(Node 2', Node 3')
  • AppendChild(Node 2', Node 4)
  • CloneNode(Node 1) → Node 1'
  • AppendChild(Node 1', Node 2')


Alt text

  • Layout Calculation
    • Similar to Layout Calculation during Initial Render
  • Tree Promotion (New Tree → Next Tree)
    • Similar to Tree Promotion during Initial Render.

Alt text

  • Tree Promotion (Next Tree → Rendered Tree)
  • Tree Diffing
    • diff between the “previously rendered tree” (T) and the “next tree” (T')
    • UpdateView(Node 3', {backgroundColor: '“yellow“})
  • View Mounting

Alt text

Now we can answer the question:

What's the problem of first demo?



  • This animation will be handled by the Javascript thread.
    • Animation will get stuck because it executes rendering procedure(render, commit, mount) in every frame
  • State updates are asynchronous.
    • This means in our case that these updates might not come at the time we think they will.

React Native Animation (Official)

Using the native driver

const translateX = useRef(new Animated.Value(0)).current

//...

Animated.timing(translateXRefValue, {
  toValue: 200,
  duration,
  easing: Easing.inOut(Easing.linear),
  useNativeDriver: true,
}).start()

  • Run in UI thread
    • without having to go through the bridge on every frame
    • new Animated.Value(0)
    • useNativeDriver: true
      • useNativeDriver property will default to false for legacy reasons
  • Once the animation has started, the JS thread can be blocked without affecting the animation.

Weex bindingx

Problems of Official React Native Animation


About Frame Per Second(pfs)




Watch Videos

60fps Record Video

vs

120fps Record Video

Why movie 24fps is ok, but 24fps in game is not ok?

https://www.zhihu.com/question/21081976

  • When you pause a movie, you can see the image is blur

  • Alt text

realistic motion blur https://css-tricks.com/how-to-create-a-realistic-motion-blur-with-css-transitions/

React Native Reanimated

https://docs.swmansion.com/react-native-reanimated/

easily create smooth animations and interactions that run on the UI thread.

  • Define animatable elements.
  • Shared values are a driving factor of all animations
    • useSharedValue hook
  • Modify shared values using animation functions like withTiming

React Native Reanimated Api

React Native ReanimatedAnimationswithTimingwithSpring...CoreuseSharedValueuseAnimatedStyle...ScrollDeviceLayout AnimationsThreadingrunOnJSrunOnUIUtilitiesinterpolate

https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started/

Watch Videos Gesture Demo

Bonus

Spring Animation

https://gaohaoyang.github.io/2021/03/01/spring-animation-framer-motion/

Summary

  • React Native Render, Commit and Mount
  • Threading Model
  • React Native Animation (Official)
  • About Frame Per Second(pfs)
  • React Native Reanimated
  • Spring Animation

Thanks


Haoyang Gao

2023.11.17