Skip to main content
Version: Next

withLoop

The withLoop modifier repeats an animation a specified number of times or infinitely. Perfect for loading indicators, pulsing effects, continuous animations, and any animation that needs to repeat.

Why Use withLoop?

Loop animations are essential for:

  • Loading indicators - Continuous spinning, pulsing
  • Attention-grabbing - Subtle animations that draw the eye
  • Ambient effects - Background animations that add life
  • Status indicators - Pulsing, breathing effects

Basic Syntax

withLoop(animation, iterations?, options?)

Parameters

  • animation (required): The animation descriptor to repeat
  • iterations (optional): Number of times to repeat. Omit or use -1 for infinite
  • options (optional): Configuration object with callbacks

Simple Example

Loop an animation infinitely:

import { useValue, withLoop, withSpring, animate } from 'react-ui-animate';

function MyComponent() {
const [rotation, setRotation] = useValue(0);

useEffect(() => {
// Infinite rotation
setRotation(withLoop(
withSpring(360),
-1 // Infinite
));
}, []);

return (
<animate.div
style={{
rotate: rotation,
width: 50,
height: 50,
background: 'teal',
}}
/>
);
}

Real-World Examples

Example 1: Loading Spinner

Continuous rotation:

function LoadingSpinner() {
const [rotation, setRotation] = useValue(0);

useEffect(() => {
setRotation(withLoop(
withSpring(360, { stiffness: 200, damping: 0 }),
-1 // Infinite
));
}, []);

return (
<animate.div
style={{
rotate: rotation,
width: 40,
height: 40,
border: '4px solid #e1e1e1',
borderTop: '4px solid #3399ff',
borderRadius: '50%',
}}
/>
);
}

Example 2: Pulsing Button

Draw attention with a pulsing effect:

function PulsingButton() {
const [scale, setScale] = useValue(1);

useEffect(() => {
setScale(withLoop(
withSequence([
withSpring(1.1, { stiffness: 200 }),
withSpring(1, { stiffness: 200 }),
]),
-1 // Infinite
));
}, []);

return (
<animate.button
style={{
scale,
padding: '12px 24px',
backgroundColor: '#3399ff',
color: 'white',
border: 'none',
borderRadius: 8,
}}
>
Click me
</animate.button>
);
}

Example 3: Breathing Animation

Subtle scale animation:

function BreathingCard() {
const [scale, setScale] = useValue(1);

useEffect(() => {
setScale(withLoop(
withSequence([
withTiming(1.05, { duration: 2000 }),
withTiming(1, { duration: 2000 }),
]),
-1 // Infinite
));
}, []);

return (
<animate.div
style={{
scale,
width: 200,
height: 200,
background: 'teal',
borderRadius: 8,
}}
>
Breathing card
</animate.div>
);
}

Example 4: Finite Loop

Repeat a specific number of times:

function BounceAnimation() {
const [y, setY] = useValue(0);

const bounce = () => {
setY(withLoop(
withSequence([
withSpring(50),
withSpring(0),
]),
3 // Repeat 3 times
));
};

return (
<>
<button onClick={bounce}>Bounce 3 Times</button>
<animate.div
style={{
translateY: y,
width: 100,
height: 100,
background: 'teal',
}}
/>
</>
);
}
import React from 'react';
import {
  useValue,
  animate,
  withTiming,
  withSequence,
  withSpring,
  withDelay,
  withDecay,
  withLoop,
} from 'react-ui-animate';
import '../../styles.css';

export default function BasicExample() {
  const [obj, setObj] = useValue({ x: 0, y: 0, width: 100, height: 100 });

  return (
    <div className="container">
      <button
        className="button buttonPrimary"
        onClick={() =>
          setObj(
            withLoop(
              withSequence([
                withTiming({ x: 100 }),
                withTiming({ y: 100 }),
                withTiming({ x: 0 }),
                withTiming({ y: 0 }),
              ]),
              5,
              {
                onStart() {
                  console.log('Loop started');
                },
                onComplete() {
                  console.log('Loop completed');
                },
              }
            )
          )
        }
      >
        Start
      </button>
      <button
        className="button buttonSecondary"
        onClick={() => setObj({ x: 0, y: 0, width: 100, height: 100 })}
      >
        Reset
      </button>

      <animate.div
        style={{
          width: obj.width,
          height: 100,
          backgroundColor: 'teal',
          margin: '20px auto 0',
          translateX: obj.x,
          translateY: obj.y,
          borderRadius: 8,
        }}
      />
    </div>
  );
}

Looping Different Animation Types

You can loop any animation type:

// Loop a spring animation
setX(withLoop(withSpring(100), 5));

// Loop a timing animation
setX(withLoop(withTiming(100, { duration: 300 }), -1));

// Loop a sequence
setX(withLoop(
withSequence([
withSpring(50),
withTiming(100),
withSpring(150),
]),
3
));

// Loop a decay (creates continuous motion)
setX(withLoop(withDecay(0.5), -1));

Using Callbacks

Track loop progress:

function LoopWithCallbacks() {
const [scale, setScale] = useValue(1);

useEffect(() => {
setScale(withLoop(
withSequence([
withSpring(1.1),
withSpring(1),
]),
5, // 5 iterations
{
onStart: () => {
console.log('Loop started');
},
onComplete: () => {
console.log('Loop completed (5 times)');
},
}
));
}, []);

return (
<animate.div
style={{
scale,
width: 100,
height: 100,
background: 'teal',
}}
/>
);
}

Common Patterns

Pattern 1: Infinite Rotation

const [rotation, setRotation] = useValue(0);

useEffect(() => {
setRotation(withLoop(
withSpring(360, { stiffness: 200, damping: 0 }),
-1
));
}, []);

Pattern 2: Pulsing Scale

const [scale, setScale] = useValue(1);

useEffect(() => {
setScale(withLoop(
withSequence([
withTiming(1.1, { duration: 1000 }),
withTiming(1, { duration: 1000 }),
]),
-1
));
}, []);

Pattern 3: Bouncing Animation

const [y, setY] = useValue(0);

const bounce = () => {
setY(withLoop(
withSequence([
withSpring(30, { stiffness: 300 }),
withSpring(0, { stiffness: 300 }),
]),
3 // Bounce 3 times
));
};

Pattern 4: Color Pulse

const [brightness, setBrightness] = useValue(1);

useEffect(() => {
setBrightness(withLoop(
withSequence([
withTiming(1.2, { duration: 1000 }),
withTiming(1, { duration: 1000 }),
]),
-1
));
}, []);

// Use in style
style={{
filter: `brightness(${brightness})`,
}}

Best Practices

✅ Do

  • Use infinite loops for loading indicators and ambient effects
  • Use finite loops for attention-grabbing animations (3-5 times)
  • Keep looped animations subtle - they're always visible
  • Use withSequence inside loops for complex repeating patterns

❌ Don't

  • Don't loop animations that are too fast or jarring
  • Don't use infinite loops for user-triggered actions
  • Don't forget to provide a way to stop infinite loops when needed
  • Don't loop layout properties - use transforms instead

Performance Tips

  1. Loops are efficient - Optimized for continuous animations
  2. Use transforms - rotate, scale are GPU-accelerated
  3. Keep it simple - Simple looped animations perform better
  4. Consider stopping - Provide a way to stop infinite loops

Troubleshooting

Animation doesn't loop:

  • Check that iterations is set correctly (-1 for infinite)
  • Verify the animation completes before looping

Loop feels janky:

  • Use transforms instead of layout properties
  • Simplify the animation inside the loop
  • Check for other heavy operations

Can't stop the loop:

  • Store the animation reference
  • Use a state variable to control when to loop
  • Consider using useEffect cleanup

Next Steps