DEV Community

Cover image for TikTok Animation in React Native
Marius Reimer
Marius Reimer

Posted on

2 1

TikTok Animation in React Native

The TikTok app has a pretty interesting loading animation. It has two horizontally aligned circles that seem to rotate its positions, seemingly in a circle. I wanted to create a similar behavior in React Native. The source code can be found here.

Initially, the spinner consists of two colored circles.

image

As soon as the red circle moves “below” the blue one, its overlapping shape turns into the background color. A similar “black” effect could be achieved by using the mix-blend-mode CSS property, if you are on the web. However, this does not exist in React Native.

image

The red circle moves all the way through the blue circle.

image

At half time, the red circle is left to the red circle. This triggers the back animation, so that the red circle moves back to its initial position.

image

The Code

First, we need to declare the shared variables. For managing the animated values, we use the useSharedValue hook. This has a behavior similar to React.useRef, means that is does not trigger re-render of the component. The react-native-reanimated library uses the JavaScript Interface (JSI), which means good performance due to synchronous JavaScript <-> Native calls.

import { useSharedValue } from 'react-native-reanimated';
const RADIUS = 30;
const currTime = useSharedValue(1);
const x = {
first: useSharedValue(RADIUS + 10),
second: useSharedValue(RADIUS * 3 + 10),
};
const radius = {
first: useSharedValue(RADIUS),
second: useSharedValue(RADIUS),
};

The following code shows how to change a shared animated value. After the component has been mounted, we start the animation timer. Using a combination of withRepeat, withSequence and withTiming, the timer counts from 1 to -1 and back, in a loop.

import { withRepeat, withSequence, withTiming } from 'react-native-reanimated';
React.useEffect(() => {
currTime.value = withRepeat(
withSequence(
withTiming(-1, { duration: 800 }),
withTiming(1, { duration: 800 }),
),
-1,
);
}, []);

Depending on the current time value, we need to change the x and radius values. The library changes values automatically, means that you do not need to trigger most things, following the declarative concept. However, you need to put those changes (to change animated style or props) in specific hooks. In this example, we only need to change the component's props, so we will use useAnimatedProps.

To actually change radius and x, we will use interpolation. We will simply "map" the input range from -1 to 1 to a specific output range. For x, we switch both circle's positions. For the radius, we change according to the initial value. Similar things apply to the second circle.

import { interpolate, useAnimatedProps } from 'react-native-reanimated';
const firstProps = useAnimatedProps(() => {
x.first.value = interpolate(
currTime.value,
[-1, 1],
[RADIUS + 10, RADIUS * 3 + 10],
);
radius.first.value = interpolate(
currTime.value,
[-1, -0.6, 0, 0.6, 1],
[RADIUS * 0.8, RADIUS * 1, RADIUS * 1.2, RADIUS * 1, RADIUS * 0.8],
);
return {
cx: x.first.value,
r: radius.first.value,
};
});

Animate React Native SVG

In order to change component props via animated shared values, you need to pass the useAnimatedProps output to the component. This will only work, when the component is actually animated via Animated.createAnimatedComponent and you change native props of native views.

import Animated from 'react-native-reanimated';
import Svg, { Circle as _Circle } from 'react-native-svg';
const Circle = Animated.createAnimatedComponent(_Circle);
render() {
return(
<Svg>
<Circle cy="40" fill="green" animatedProps={firstProps} />
</Svg>
)
}

The circles are now animated, but still need to have the clipping effect. In order to achieve this, we need to define a ClipPath mask, that includes both animated circles.

import Animated from 'react-native-reanimated';
import Svg, { Circle as _Circle, Defs, ClipPath } from 'react-native-svg';
const Circle = Animated.createAnimatedComponent(_Circle);
render() {
return(
<Svg>
<Defs>
<ClipPath id="clip">
<Circle cy="40" animatedProps={firstProps} />
<Circle cy="40" animatedProps={secondProps} />
</ClipPath>
</Defs>
...
</Svg>
)
}

Finally, we will render three circles. First is for the render red circle. The last two are for the green one and its background, which have the same position and size. The green circle applies the clip path. As you might notice, one output of useAnimatedProps could only be applied to one component, so the props had to be duplicated. This feels like a hack, but makes sense if you view it from a native perspective.

Originally published at https://mariusreimer.com on December 10, 2020.

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

Top comments (0)

Sentry mobile image

Improving mobile performance, from slow screens to app start time

Based on our experience working with thousands of mobile developer teams, we developed a mobile monitoring maturity curve.

Read more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay