TikTok Animation in React Native

December 10, 2020 - 3 min read

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.

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.

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

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.

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(
withTiming(-1, { duration: 800 }),
withTiming(1, { duration: 800 }),
}, []);

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(
[-1, 1],
[RADIUS + 10, RADIUS * 3 + 10],
radius.first.value = interpolate(
[-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() {
<Circle cy="40" fill="green" animatedProps={firstProps} />

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() {
<ClipPath id="clip">
<Circle cy="40" animatedProps={firstProps} />
<Circle cy="40" animatedProps={secondProps} />

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.