Search code examples
javascriptreactjsreact-nativereact-native-reanimatedreact-native-reanimated-v2

I'm trying to animate a circle expanding, staying at maximum width and height for 4 seconds, then shrink again to original size, then repeat again


I'm trying to animate a 50px circle to expand to 100px over 2 seconds, then stay at 100px for 2 seconds before starting to shrink down to 50px over 2 seconds. Then repeat this indefinitely.

Currently I've nailed the expanding part, but I can't figure out how to keep it at max width/height for 2 seconds and then shrink it down.

import React, { FunctionComponent, useEffect } from 'react';
import Animated, {
    useAnimatedStyle,
    useSharedValue,
    withDelay,
    withRepeat,
    withSpring,
    withTiming,
} from 'react-native-reanimated';
import styled from 'styled-components/native';

const CircleAnimation: FunctionComponent<Props> = () => {
    const widthAndHeight = useSharedValue(50);
    const duration = 2;

    const foregroundCircleStyle = useAnimatedStyle(() => {
        return {
            width: widthAndHeight.value,
            height: widthAndHeight.value,
            borderRadius: widthAndHeight.value / 2,
        };
    });

    useEffect(() => {
        widthAndHeight.value = withDelay(
            2000,
            withRepeat(
                withTiming(100, {
                    duration: duration * 1000,
                }),
                -1,
                false
            )
        );
    }, [duration, widthAndHeight]);

    return <ForegroundCircle style={[foregroundCircleStyle]} />
};

const ForegroundCircle = styled(Animated.View)`
    width: 50px;
    height: 50px;
    border-radius: 25px;
    background-color: yellow;
`;

export default CircleAnimation;

Solution

  • I refactored your effect to something like this:

    useEffect(() => {
        widthAndHeight.value = withRepeat(
          withSequence(
            withTiming(255, { duration: breathLength * 1000 }),
            withDelay(4000, withTiming(120, { duration: breathLength * 1000 }))
          ),
          -1
        );
      }, [breathLength, widthAndHeight]);
    

    And it seems to work like you described.

    Explaining the code:

    First, you want to repeat a certain animation forever, so you wrap the animation with the withRepeat function, passing -1 as the second argument.

    The animation you want to repeat is a sequence (withSequence) of two animations:

    • Expand the circle over 4 seconds (withTiming).
    • Wait 4 seconds (withDelay) and then shrink the circle back (withTiming).

    Also, not related to animations, but if you want a perfect circle, your border-radius should be at least width(or height, since they have the same value) divided by 2, so at least ~128px.