Documentation

Why animated background patterns keep restarting (and how to stop that)

Some patterns in the background generator have an animated version - the icon next to the pattern name switches it on in the preview, then across the whole site. If you're embedding a pattern like this in your own project and changing its color or opacity dynamically, it helps to understand three things that usually make an animation like this stutter and restart for no visible reason.

The animation lives in the image itself, not the DOM element

An animated pattern is an SVG with a built-in animation (CSS keyframes or an SMIL animate tag), wrapped in a data URI and used as a background image. The browser doesn't know that yesterday there was an "almost identical" image here - it compares the URL string itself. If even one character changed, the resource is treated as brand new: it's loaded and painted from scratch, and the animation inside restarts along with it. The DOM element the background is attached to might not have changed at all - this isn't about React or component re-rendering, it's specifically about the identity of the image string.

SVG markup has to be a pure function

If generating the SVG depends on anything variable - a random animation delay, say - then identical-looking parameters produce a different string every time. Any extra, redundant call to that function (and in a real app there are more of those than you'd expect - syncing state to the URL, for instance) quietly slips in a new data URI and restarts the animation for no apparent reason. The rule is simple: the same input must always produce byte-for-byte the same SVG output. No Math.random(), Date.now(), or anything similar inside the markup generator.

Don't bake in whatever changes often

An opacity slider or a theme accent color change are events that fire often and change real values. If color and opacity are baked directly into the SVG markup, every such event honestly generates a brand new image - and the animation stutters on every tick. Split "shape" from "color": draw the pattern once as a black-and-white mask (its URL depends only on the shape and whether animation is on), then apply the real color and opacity on top as plain CSS properties - the browser can change those instantly without touching the already-loaded mask resource.

background-color: var(--bg);

.pattern {
  background-color: var(--pattern-color);
  opacity: var(--pattern-opacity);

  mask-image: var(--pattern-mask);   /* rarely changes: shape + animation */
  mask-mode: alpha;
}

The background layer has to survive page navigation

If the background is rendered inside the part of the component tree that gets fully replaced on every page navigation (typical for multi-page SPAs), the background's DOM node is recreated from scratch each time - the same image-identity problem as in the first point, just at the level of the whole element. Keep a layer like this at the very top of the tree, in a place that doesn't depend on the current page and mounts once per session instead of remounting on every navigation.

// Top of the app - rendered once per session
function AppShell({ children }) {
  return (
    <>
      <BackgroundLayer />
      {children}
    </>
  )
}

In short

Three sources of an animation restarting out of nowhere: the background's DOM node gets recreated on navigation; the SVG generator isn't a pure function (say, it uses random values); or color and opacity are baked into the image itself and change alongside ordinary user interactions. Remove all three, and the animation keeps playing for exactly as long as you intended, no matter what's happening around it.

Export