How to handle complex animations in React Native?

How to handle complex animations in React Native?

  • Why?
  • Challenges!
  • Demonstration </>

Why?

  • Enhances the UX of our app
  • Gives an immersive experience to our user
  • Increases our user engagement
  • Attracts attention to important elements and functions
  • Helps identify connections between components
  • Give feedback to users so they know what’s happening

Challenges!

  • Animations are quite overwhelming at first
  • Quite complex and difficult to maintain 60FPS
  • JS vs. Native thread w/ Business Logic
  • Supporting device of all dimensions
  • Phobia of learning a new API()

Phobia of the new API()

import {Animated} from 'react-native';

const opacity = new Animated.Value(0);

Animated.timing(opacity, {
  toValue: 1,
  delay: 500,
  duration:2000,
  useNativeDriver:true,
}).start();

<Animated.View style={{opacity}}>
  <Text>Took 2 seconds to fade me in!</Text>
</Animated.View>

Parallel Animations

import {Animated} from 'react-native';

const opacity = new Animated.Value(0);
const translateY = new Animated.Value(-100);

Animated.parallel([
  Animated.timing(opacity, {
    toValue: 1,
    duration: 2000,
    useNativeDriver: true,
  }),
  Animated.timing(translateY, {
    toValue: 0,
    duration: 2000,
    useNativeDriver: true,
  }),
]).start();

<Animated.View style={{opacity, transform: [{translateY}],}}>
  <Text>Slide down and and fade in!</Text>
</Animated.View>

Animation Sequence

import {Animated} from 'react-native';

const opacity = new Animated.Value(0);
const translateY = new Animated.Value(-100);

Animated.sequence([
  Animated.timing(opacity, {
    toValue: 1,
    useNativeDriver: true,
  }),
  Animated.delay(1000),
  Animated.timing(translateY, {
    toValue: 0,
    useNativeDriver: true,
  }),
]).start();

<Animated.View style={{opacity, transform: [{translateY}],}}>
  <Text>Slide down and and fade in!</Text>
</Animated.View>

Demonstration </>

Demonstration </>

Splash Screen Structure

const {
  top: paddingTop,
  bottom: paddingBottom,
} = useSafeArea();

useEffect(() => {
  setRandomImage(
    Math.floor(Math.random() * Math.floor(images.length)),
  );
}, []);

<View
  style={[
    styles.container,
    {paddingTop, paddingBottom}
  ]}

  onLayout={({nativeEvent}) => {
    const {height} = nativeEvent.layout;
    const CONTAINER_TOP_Y = (height - IMAGE_HEIGHT) / 2;

    setTranslateValue(-(CONTAINER_TOP_Y - HEADER_HEIGHT - paddingTop));
  }}
>
  <Animated.View
    style={[
      {paddingTop},
      styles.splashHeader,
    ]}
  />
 
  <Animated.View
    style={[
      styles.homeHeader,
      {opacity, paddingTop}
    ]}
  />
  
  <Animated.Image
    source={images[randomImage]}
    style={[styles.splashImage, {transform: [{translateY}]}]}
  />   
</View>

Splash Screen Breakdown

Animated.parallel([
  Animated.timing(opacity, {
    toValue: 1,
    useNativeDriver,
  }),
  Animated.timing(translateY, {
    toValue: translatevalue,
    useNativeDriver,
  }),
]).start(() => navigation.navigate('Home', {splashImage}));

Home Image Structure

<Animated.View style={[styles.border, {opacity}]} />
  
<Image
  style={[styles.splashImage]}
  source={splashScreenImages[splashImage]}
/>

<Animated.View style={[styles.border, {opacity}]} />

// PSEUDO_IMAGE_BORDER = (DEVICE_WIDTH - IMAGE_WIDTH) / 2;
{showAbsolute && (
  <Animated.View
    style={[
      styles.leftAbsolute,
      {transform: [{translateX: translateXLeft}]},
    ]}
  />
)}

// PSEUDO_IMAGE_BORDER = (DEVICE_WIDTH - IMAGE_WIDTH) / 2;
{showAbsolute && (
  <Animated.View
    style={[
      styles.rightAbsolute,
      {transform: [{translateX: translateXRight}]},
    ]}
  />
)}

Home Image Breakdown #1

Animated.parallel([
  Animated.timing(opacity, {
    toValue: 1,
    useNativeDriver,
  }),
  Animated.timing(translateXLeft, {
    toValue: -PSEUDO_IMAGE_BORDER,
    useNativeDriver,
  }),
  Animated.timing(translateXRight, {
    toValue: PSEUDO_IMAGE_BORDER,
    useNativeDriver,
  }),
]).start(() => setShowAbsolute(false));

Home Image Breakdown #2

<Animated.View
  style={[
    styles.bgWhite,
    StyleSheet.absoluteFill,
    {opacity: overlayOpacity},
  ]} 
/>
    
Animated.parallel([
  ...opacity,
  ...translateXLeft,
  ...translateXRight,

  Animated.timing(overlayOpacity, {
    toValue: 0.4,
    useNativeDriver,
  }),
]).start(() => setShowAbsolute(false));

Home Image Breakdown #3

<Animated.Image
  source={splashScreenImages[splashImage]}
  blurRadius={Platform.OS === 'ios' ? 10 : 2}
  style={[
   styles.splashImage,
   StyleSheet.absoluteFill, {opacity},
  ]}
/>
    
Animated.parallel([
  ...opacity,
  ...translateXLeft,
  ...translateXRight,
  ...overlayOpacity,
]).start(() => setShowAbsolute(false));

Home Image Breakdown #4

<Animated.View
  style={[
    {opacity},
    StyleSheet.absoluteFill,
    styles.headingContainer,
  ]}
>
  <Text style={styles.heading}>
    ALL OF THE{'\n'}ANIMATIONS ARE{'\n'}RUNNING ON{'\n'}NATIVE THREAD!
  </Text>

  <View style={[styles.getInTouchContainer]}>
    <Text style={[commonStyles.MontserratRegular, styles.getInTouch]}>
      YOU CAN GET IN TOUCH WITH ME BELOW!
    </Text>
  </View>
</Animated.View>
    
Animated.parallel([
  ...opacity,
  ...translateXLeft,
  ...translateXRight,
  ...overlayOpacity,
]).start(() => setShowAbsolute(false));

Home Buttons Structure

<View
  style={[
    styles.flex1,
    styles.flexRow
  ]}
  onLayout={({nativeEvent}) => {
  const {height} = nativeEvent.layout;

  setAnimate(true)
  translateY.setValue(-height);
  }}
>
// const translateX = new Animated.Value(DEVICE_WIDTH);
  <Animated.View
    style={[
      styles.horizontalBorder,
      {transform: [{translateX}]}
    ]}
  />

  <View style={[styles.leftWhiteBorder]} />
    
  <View style={styles.flex1}>
//  const translateY = new Animated.Value(0);
    <Animated.View
      style={[
        styles.verticalBorder,
        {transform: [{translateY}]}
      ]}
    />
       
    {BUTTONS.map((row) => (
      <Animated.View
        style={[
          {opacity}
          styles.flex1,
          commonStyles.flexRow,
        ]}
      >
      // buttons
      </Animated.View>
    ))}
  </View>

  <View style={[styles.rightWhiteBorder]} />
</View>

Home Buttons Breakdown

const opacity = new Animated.Value(0);

if (animate) {
  Animated.parallel([
    Animated.timing(opacity, {
      toValue: 1,
      delay: 250,
      useNativeDriver,
    }),
    Animated.timing(translateY, {
      toValue: 0,
      useNativeDriver,
    }),
    Animated.timing(translateX, {
      toValue: 0,
      useNativeDriver,
    }),
  ]).start();
}

ULIMTATE GOAL

  • No frame drops
  • Achieve butter smooth 60 FPS animations without writing any native code!
  • Responsive App design + animations
setInterval(() => {
  for (var i = 0; i < 5e8; i++) {}
}, 1000);

ANY QUESTIONS?

Thank you very much!

vinaysharma14

Handling complex animations in React Native

By Vinay Sharma

Handling complex animations in React Native

  • 333