Free · Fast · Privacy-first

CSS Infinite Animation

An infinite CSS animation loops continuously until the element is removed or the animation is paused.

Automatic start and end state matching to prevent loop jump

🔒

Compositor-thread properties only for jank-free looping

Built-in animation-play-state: paused hover option

Generates prefers-reduced-motion override for accessibility

Cost
Free forever
Sign-up
Not required
Processing
In your browser
Privacy
Files stay local
FreeNo signupWhite-label

Add this Animation Builder to your website

Drop the Animation Builder into any page — blog post, product docs, intranet, school portal — with a single line of HTML. Your visitors get the full tool, processed entirely in their browser. No backend, no uploads, no signup.

  • Files stay 100% in the visitor's browser
  • Responsive — adapts to any container width
  • Free forever, no API key needed

Embed code

<iframe
  src="https://www.fixtools.io/css-tool/animation-builder?embed=1"
  width="100%"
  height="780"
  frameborder="0"
  style="border:0;border-radius:16px;max-width:900px;"
  title="Animation Builder by FixTools"
  loading="lazy"
  allow="clipboard-write"
></iframe>

Attribution-friendly: a small "Powered by FixTools" link appears in the embed footer.

How Infinite CSS Animations Work and How to Keep Them Smooth and Accessible

An infinite CSS animation uses animation-iteration-count: infinite, which instructs the browser to restart the @keyframes sequence immediately after it completes, cycling continuously. The animation runs until the element is removed from the DOM, the animation-play-state is set to paused, or the animation class is removed. For the loop to be visually seamless, the element's state at 100% must match its state at 0%. A rotate animation from 0deg to 360deg is inherently seamless because 360 degrees and 0 degrees are visually identical. A translate animation that moves an element from translateY(0) to translateY(-20px) will jump back to the start position at each iteration unless a return to translateY(0) is included in the @keyframes. The alternative is animation-direction: alternate, which plays the keyframes forward then backward on successive iterations, guaranteeing a smooth return to the start state without a jump.

Preventing jank in infinite animations requires limiting animated properties to transform and opacity. These two properties run entirely on the compositor thread, meaning the GPU handles each frame without triggering layout or paint on the main thread. An infinite animation on background-color, box-shadow, border, or width triggers a paint recalculation on every single frame for the lifetime of the animation. On a page where a spinner or pulse indicator is running continuously for 10 to 30 seconds while content loads, the paint cost of a non-compositor animation adds up and can affect the performance of other page interactions. Every indefinitely looping animation should use only transform and opacity to ensure it has no ongoing performance impact on the page.

Pausing infinite animations addresses two concerns. The first is user experience: looping animations that cover interactive content can interfere with the user's ability to read or interact. Applying animation-play-state: paused on :hover lets users pause a looping animation simply by resting the cursor on the element. The second concern is accessibility. The prefers-reduced-motion media query disables animations for motion-sensitive users when set to animation: none inside the query block. For infinite animations specifically, this is critical: an element that animates indefinitely is exactly the type of motion that causes vestibular symptoms in sensitive users. Both the hover-pause and the reduced-motion override should be included with every infinite CSS animation.

The choice of cycle duration shapes how an infinite animation reads to the viewer over time. Very fast loops below 0.8 seconds register as urgency or activity and are appropriate for spinners and live data indicators where the implied state is in progress right now. Medium loops between 1 and 2 seconds read as steady, ambient liveness and work for online status dots, breathing CTAs, and gentle indicators. Slow loops above 3 seconds read as decorative atmosphere rather than functional signal and are best for hero illustrations, background gradients, and ambient environmental effects. Mixing very fast and very slow loops in the same view creates a visual hierarchy where the fast element draws urgent attention while the slow elements contribute peripheral motion, but using all three speed bands in a single screen produces a chaotic interface where no single element commands focus.

How to use this tool

💡

Select a loop type: rotate, pulse, float, or marquee. Set duration for one full cycle. Enable the pause-on-hover option if the animation should stop when the user interacts. Copy the complete loop CSS including the reduced-motion override.

How It Works

Step-by-step guide to css infinite animation:

  1. 1

    Choose the loop animation type

    Select rotate for spinners and icons, pulse for indicators, float for ambient vertical movement, or marquee for horizontal scrolling text and logos.

  2. 2

    Set the cycle duration

    Enter the duration for one full loop cycle: 0.8s for a fast spinner, 1.5s for a standard pulse, 3 to 5s for a slow ambient float.

  3. 3

    Enable pause-on-hover and reduced-motion override

    Turn on the pause-on-hover option to include animation-play-state: paused on :hover. Confirm the prefers-reduced-motion override is included in the generated output.

  4. 4

    Verify seamless looping in the preview

    Watch the preview through at least three complete cycles. Look for a visible jump at the iteration boundary. Adjust the @keyframes end state if a jump is visible.

Real-world examples

Common situations where this approach makes a real difference:

Indefinite loading spinner

A web application shows an infinite rotate animation on a circular spinner during server-side data processing of unknown duration. The spinner uses transform: rotate(0deg) to rotate(360deg) with linear timing and a 1-second cycle. The loop is seamless because 0 and 360 degrees are visually identical. The spinner is removed from the DOM when the response arrives, stopping the animation by removing the element entirely.

Ambient floating illustration

A SaaS empty state shows an SVG illustration with a slow 4-second float animation using translateY(0) to translateY(-10px) with animation-direction: alternate. The alternate direction produces a smooth up-and-down motion without a jump at the loop point. The long cycle duration keeps the motion calm and non-distracting. The animation pauses on hover and stops entirely under the prefers-reduced-motion query.

Logo marquee scroll

A social proof section scrolls customer logos horizontally using an infinite translateX animation on a flex row wider than the viewport. The logos are duplicated so the second copy fills the viewport as the first scrolls offscreen, creating a seamless loop. The animation uses linear timing for constant scroll speed. The marquee pauses on hover, letting users read individual logo names without the logos moving.

Live status indicator pulse

A collaboration tool shows a green pulsing dot next to each online user. The dot uses an infinite scale pulse from scale(1) to scale(1.3) at 0.8s with animation-direction: alternate. When the user goes offline, the animation class is removed immediately, leaving a static grey dot. The fast 0.8s cycle signals real-time activity. The reduced-motion override replaces the pulse with a static green dot for users who have requested reduced motion.

When to use this guide

Use this for loading spinners, ambient background effects, pulsing indicators, marquee scrolls, and any animation that should run continuously without requiring repeated user interaction to restart.

Pro tips

Get better results with these expert suggestions:

1

Use animation-direction: alternate to avoid loop jumps without extra keyframes

For any two-state infinite animation (pulse, float, breathe), alternate direction makes the animation reverse smoothly rather than jumping back to 0%. This halves the amount of keyframe code needed and ensures the loop is always seamless, even if the 0% and peak values differ significantly.

2

Only use transform and opacity in infinite animations

Paint-triggering properties in an infinite loop fire a GPU paint on every single frame for the entire lifetime of the animation. On a page with a spinner running for 20 seconds, this is 1,200 paint calls. Stick to transform and opacity to keep the compositor thread isolated from layout and paint work.

3

Remove infinite animations from the DOM when they are no longer needed

Setting animation: none or animation-play-state: paused stops the animation but leaves the element in the DOM consuming memory for the compositor layer. If the animation will not be needed again (a spinner after a load completes), remove the element entirely to release the compositor layer and free GPU memory, especially important on pages with many simultaneous looping elements.

4

Test infinite animations for loop jump using DevTools slow-motion playback

In the DevTools Animations panel, slow the playback to 10% and watch the animation through at least two full cycle boundaries. A loop jump that is invisible at full speed becomes obvious at slow motion. Identify and fix it before shipping: match the 0% and 100% values or switch to animation-direction: alternate.

FAQ

Frequently asked questions

Set animation-iteration-count: infinite in the animation shorthand or as a separate property. In the shorthand, infinite is placed in the iteration count position: animation: spin 1s linear infinite. The animation restarts immediately after the @keyframes sequence completes and continues until the element is removed from the DOM, the animation-play-state is set to paused, or the animation property is removed. There is no time limit or iteration cap when infinite is used.
A jump at the loop boundary happens when the element's state at 100% differs from its state at 0%. The browser restarts from 0% immediately after reaching 100%, and if those values differ visually, the jump is visible. Fix it by ensuring the 0% and 100% keyframe values match, or use animation-direction: alternate, which plays the keyframes in reverse on even iterations so the animation always returns smoothly to the start state via the reverse path of the forward animation.
animation-direction: alternate plays the @keyframes forward on odd iterations and backward on even iterations. For a two-state animation (a pulse that grows and shrinks), this eliminates the need to declare the return-to-base keyframe. The animation plays to the peak state and then automatically reverses back to the base state on the next iteration. This creates smooth, continuous oscillation with minimal keyframe code. It doubles the perceived cycle time: a 1-second animation with alternate takes 2 seconds to complete one full forward-and-back cycle.
Yes. Add a :hover selector to the animated element with animation-play-state: paused. When the cursor rests on the element, the animation freezes at its current frame. When the cursor leaves, the animation resumes from where it paused. This requires no JavaScript. The CSS is: .animated-element:hover { animation-play-state: paused; }. This gives users control over continuously looping animations without requiring any interactive controls, and it partially addresses the WCAG requirement for user control over motion.
An infinite animation using only transform and opacity has no ongoing performance impact because both properties run on the compositor thread. The GPU handles each frame independently without involving the CPU main thread. An infinite animation on a paint-triggering property (box-shadow, filter, background-color, width) fires a paint recalculation on every frame for as long as the animation runs. For a spinner running for 10 seconds, that is 600 paint operations. On a complex page, this can reduce the frame rate of other interactions. Always use transform and opacity for infinite animations.
You cannot stop a CSS animation after a specific duration using CSS alone; the iteration count is a count of full cycles, not a time limit. To stop after a specific duration, use JavaScript: set a setTimeout that adds a class which sets animation: none after the desired duration. Alternatively, set a very high iteration count rather than infinite: animation-iteration-count: 999 runs the animation 999 times before stopping. At a 1-second cycle, that is 999 seconds, which effectively never stops in practice while not being truly infinite.
Set the same animation-delay and animation-duration on all elements that should be in phase. Because CSS animations all start from 0% at their delay point, elements with the same duration and no delay are inherently in phase. To put elements intentionally out of phase (for a wave effect), use negative delays: a three-element wave with a 1-second cycle uses delays of 0s, -0.33s, and -0.66s to spread the elements evenly across the cycle. The negative delay starts each element at a different point in the loop, creating the wave offset without requiring separate @keyframes.
Two approaches are required together. First, include a @media (prefers-reduced-motion: reduce) block that sets animation: none on every infinitely looping element. This completely disables the motion for users who have enabled reduce-motion in their OS settings. Second, add animation-play-state: paused on the :hover selector to give all users the ability to stop the animation by resting the cursor on it, even if they have not changed their OS settings. These two measures together address both the formal WCAG accessibility requirement and the practical user experience concern for motion-sensitive visitors who have not enabled the OS-level preference.
Modern browsers automatically throttle or pause CSS animations when their tab is hidden, as part of the Page Visibility optimization that reduces battery drain on inactive tabs. This is handled by the browser without any developer intervention: an infinite animation that has been running for an hour in a hidden tab will not consume CPU or GPU resources during that hour. When the user returns to the tab, the animation resumes from where the browser last rendered it. This behaviour is one of the reliability advantages of CSS animations over JavaScript-driven equivalents, which require explicit visibilitychange event handling to achieve the same battery-saving result.
Yes, by updating the animation-duration value at runtime. The cleanest pattern uses a CSS custom property: --spin-duration: 1s on the element selector, referenced in the animation shorthand as animation: spin var(--spin-duration) linear infinite. To change the speed, set element.style.setProperty('--spin-duration', '0.5s'). The browser interpolates the new duration starting from the current frame without restarting the animation, so there is no visible jump. This pattern is useful for spinners that need to speed up during heavy load or progress indicators that need to slow down as the operation nears completion.
No. The animation-timing-function applies between keyframe stops and does not have a separate value at the iteration boundary. For a seamless loop, the timing function should be linear or a symmetric easing curve that produces a continuous motion across the boundary. An ease-in-out curve on a non-alternate infinite animation creates a visible deceleration at the end of each cycle followed by an acceleration at the start of the next, which reads as a stuttering loop rather than continuous motion. For genuinely seamless rotation or scroll loops, use linear. For oscillating pulses where alternate direction handles the reversal, ease-in-out is fine because the slow points fall at the alternation moments which read as natural endpoints.

Related guides

More use-case guides for the same tool:

Ready to get started?

Open the full Animation Builder — free, no account needed, works on any device.

Open Animation Builder →

Free · No account needed · Works on any device