useAnimation
useAnimation is a hook that helps create accessible animations by honouring the user Reduce Motion preference.
This hook uses the react-native built-in Animated API, check here for accessible animations in Reanimated.
Usage
import { useAnimation } from 'react-native-ama';
const { animatedStyle, progress, play } = useAnimation({
  duration,
  useNativeDriver,
  from: {},
  to: {},
  skipIfReduceMotionEnabled,
});
| Property | Type | Description | 
|---|---|---|
| duration | number | the animation duration | 
| useNativeDriver | boolean | Using the native driver | 
| from | ViewStyle | the initial state of the animation | 
| to | ViewStyle | the final state of the animation | 
| skipIfReduceMotionEnabled | boolean | (Optional) if true, the animation will be played with duration 0 when Reduce Motionis enabled. | 
Returns
| Property | Type | Description | 
|---|---|---|
| animatedStyle | Object | the animation style to apply at the component | 
| progress | Animated.Value | The Animated.Value used to trigger the animation | 
| play | (toValue = 1) => Animated.timing | Returns the Animated.timing | 
Example
The following animations translate in, with fading, an absolute positioned view:
import React, { useRef } from 'react';
import { Animated, Dimensions, StyleSheet, View } from 'react-native';
import { Pressable, Text } from 'react-native-ama';
import { useAccessibleAnimation } from 'react-native-ama';
export const ReduceMotionScreen = () => {
  const [overlayProgressValue, setOverlayProgressValue] =
    React.useState<Animated.Value | null>(null);
  const animationProgress = useRef<Animated.Value>(
    new Animated.Value(0),
  ).current;
  const { play, animatedStyle, progress } = useAccessibleAnimation({
    duration: 300,
    useNativeDriver: true,
    from: {
      opacity: 0,
      transform: [{ translateY: 200 }],
    },
    to: {
      opacity: 1,
      transform: [{ translateY: 0 }],
    },
  });
  const overlayStyle = {
    opacity: overlayProgressValue || 0,
  };
  const playAnimation = () => {
    setOverlayProgressValue(progress);
    play().start();
  };
  return (
    <>
      <View style={styles.container}>
        <Pressable title="Test Animation 1" onPress={playAnimation1} />
      </View>
      {overlayProgressValue ? (
        <Pressable
          accessibilityRole="button"
          accessibilityLabel="Close popup"
          style={StyleSheet.absoluteFill}
          onPress={() => reverseAnimation()}>
          <Animated.View style={[styles.overlay, overlayStyle]} />
        </Pressable>
      ) : null}
      <Animated.View style={[styles.animation1, animatedStyle]}>
        <Text style={styles.text}>Content goes here</Text>
      </Animated.View>
    </>
  );
};
This is the result when we play the animation with Reduce Motion off and on:
| Reduce Motion: off | Reduce Motion: on | | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | |
| | |When reduce Motion is off, the animation is played as specified; while when is on any motion animation is played instantaneity (using a duration of 0), while other properties, like opacity, are played as specified.
Sequential animation
Let's consider the following animation:
The animation is defined as:
const {
  play: play2,
  animatedStyle: animatedStyle2,
  progress: progress2,
} = useAccessibleAnimation({
  duration: 300,
  useNativeDriver: false,
  from: {
    opacity: 0,
    width: 0,
    left: MAX_LINE_WIDTH / 2,
  },
  to: {
    opacity: 1,
    width: MAX_LINE_WIDTH,
    left: theme.padding.big,
  },
});
const { play: play3, animatedStyle: animatedStyle3 } = useAccessibleAnimation({
  duration: 300,
  useNativeDriver: false,
  from: {
    height: 2,
    marginTop: -1,
  },
  to: {
    height: 200,
    marginTop: -100,
  },
});
const playAnimation = () => {
  play2().start(() => {
    play3().start();
  });
};
It's a two-part animation, where the first one the animates the view width and opacity:
from: {
  opacity: 0,
  width: 0,
  left: MAX_LINE_WIDTH / 2,
},
to: {
  opacity: 1,
  width: MAX_LINE_WIDTH,
  left: theme.padding.big,
}
The second one the height:
from: {
  height: 2,
  marginTop: -1,
},
to: {
  height: 200,
  marginTop: -100,
},
Let's play the animation when reduce motion is enabled:
The animation doesn't look right. The first animation is played correctly, but:
- the width animation is played with a duration of 0s
- the fade animation is played with a duration of 300ms
After that, the height jumps instantly from 2 to 200.
skipIfReduceMotionEnabled
One way to fix the animation is using the skipIfReduceMotionEnabled parameter; as this makes all the animations defined to be played instantly:
const {
  play: play2,
  animatedStyle: animatedStyle2,
  progress: progress2,
} = useAccessibleAnimation({
  duration: 300,
  useNativeDriver: false,
  skipIfReduceMotionEnabled: true,
  from: {
    opacity: 0,
    width: 0,
    left: MAX_LINE_WIDTH / 2,
  },
  to: {
    opacity: 1,
    width: MAX_LINE_WIDTH,
    left: theme.padding.big,
  },
});
The result is: