← Back to gallery
CSS

Moving Background Reveal

Layered background gradient drifts behind a static foreground composition — three motion mechanics for ambient hero spaces: a slow drift, a pulse-and-settle, and a long horizontal sweep. Foreground content stays put; only the background layers move.

backgroundambient-motionlinear-gradienttransformlayered-bgno-layout-shiftprefers-reduced-motion

Background-position reveal · static viewport

Moving Background Reveal

Three production-realistic ways to animate a background-image inside a stationary viewport — a horizontal scene pan, a vertical tape, and a two-layer parallax that uses multi-background-image with per-layer durations to create depth from a single element.

X-axis pan · landscape reveal

Wide Horizon Pan

A wide repeating gradient strip pans horizontally inside a fixed viewport. Background-size is wider than the viewport so the background-position cycle (0% → 100%) reveals more of the scene over time. Classic route-map / landscape banner pattern.

  • background-position
  • X-axis
  • landscape

Y-axis pan · conveyor strip

Vertical Tape

A vertically repeating pattern slides upward (or downward) inside the same fixed viewport. Useful for conveyor belts, ticker columns, and any vertical-flow brand surface. The repeat unit is sized so the seam returns cleanly at every cycle.

  • background-position
  • Y-axis
  • conveyor

Multi-layer · depth-of-field

Parallax Layers

Two background images stacked with multi-background syntax, each panning at a different rate so the closer layer (foreground specks) sweeps faster than the farther layer (atmospheric streak). All from a single element — no JavaScript, no extra DOM nodes.

  • multi-background
  • parallax
  • depth

Moving background inspector

Wide Horizon Pan

  • background-position
  • X-axis
  • landscape

A wide repeating gradient strip pans horizontally inside a fixed viewport. Background-size is wider than the viewport so the background-position cycle (0% → 100%) reveals more of the scene over time. Classic route-map / landscape banner pattern.

Helped you ship something? 🐟 Send my cat a churu

/* Single background-image; background-size 220% × 100%; background-position X animates from 0% to 100%. */
.moving-bg-surface {
  --bg-duration: 9.00s;
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(90deg, rgba(5,8,16,0.28), rgba(5,8,16,0.62)),
    repeating-linear-gradient(100deg,
      transparent 0 18px,
      var(--bg-accent-a, #67e8f9) 18px 22px,
      transparent 22px 60px,
      var(--bg-accent-b, #34d399) 60px 64px,
      transparent 64px 110px);
  background-size: 220% 100%;
  background-repeat: no-repeat, repeat-x;
  animation: panX var(--bg-duration) linear infinite;
}

@keyframes panX {
  from { background-position: 0% 50%; }
  to   { background-position: 100% 50%; }
}

@media (prefers-reduced-motion: reduce) {
  .moving-bg-surface { animation: none; background-position: 50% 50%; }
}

How to make this

A moving background reveal keeps the viewport and foreground still, then animates background-position on an oversized image layer inside the clipped box.

html
1<figure class="moving-bg-reveal">  <figcaption>Route preview</figcaption></figure> <style>.moving-bg-reveal {7  position: relative;  isolation: isolate;  overflow: hidden;  display: grid;  place-items: end start;  width: min(100%, 30rem);  aspect-ratio: 16 / 9;  margin: 0;  border-radius: 18px;  background: #0e1a2c;}18.moving-bg-reveal::before {  content: "";  position: absolute;  inset: 0;  z-index: -1;  background-image:    linear-gradient(90deg, rgba(5, 8, 16, .22), rgba(5, 8, 16, .58)),    repeating-linear-gradient(100deg,      transparent 0 22px,      #67e8f9 22px 26px,      transparent 26px 72px,      #34d399 72px 78px,      transparent 78px 132px);31  background-size: 260% 100%;  background-position: 0% 50%;33  background-repeat: no-repeat, repeat-x;  animation: moving-bg-reveal-recipe-pan 9s linear infinite;}36.moving-bg-reveal::after {  content: "";  position: absolute;  inset: 0;  pointer-events: none;  background: linear-gradient(90deg, rgba(0,0,0,.5), transparent 14%, transparent 86%, rgba(0,0,0,.5));}43.moving-bg-reveal figcaption {  position: relative;  margin: 1rem;  padding: .55rem .75rem;  border-radius: 999px;  color: #f8fafc;  background: rgba(15, 23, 42, .72);  border: 1px solid rgba(255, 255, 255, .16);  font: 700 .9rem/1.1 ui-sans-serif, system-ui;}@keyframes moving-bg-reveal-recipe-pan {  from { background-position: 0% 50%, 0% 50%; }  to { background-position: 100% 50%, 100% 50%; }}57@media (prefers-reduced-motion: reduce) {  .moving-bg-reveal::before {    animation: none;    background-position: 50% 50%, 50% 50%;  }}</style>

Annotated snippet

  1. Line 1The figure is the stationary viewport. Any meaningful label is real text in the figcaption, not encoded only in the moving background.
    PitfallHow should text sit on a moving background?

    Put text on a stable surface or overlay and test the worst color position. Moving backgrounds change contrast over time, so bare text can become unreadable during part of the loop.

  2. Line 7position: relative and overflow: hidden create the clipped stage for the oversized background layer.
  3. Line 18The pseudo-element owns the moving image. Keeping it behind the foreground lets the content stay still while the scene reveals underneath.

    Moving the whole card with transform drags the "Label" off-axis with the stripes. Animating background-position only slides the paint inside a still box — the label and the box edges never move.

    PitfallWhy animate background-position instead of transform?

    Use background-position when the box and foreground must stay still while the image inside the box moves. Use transform when the whole layer can move independently without carrying text or layout with it.

  4. Line 31background-size is wider than the viewport, giving background-position real off-screen scenery to reveal during the cycle.

    Same background-position 0% → 100% animation on both cards. background-size: 100% has no off-canvas runway — the stripes look almost frozen because the pattern starts and ends at the same visible spot. 240% gives the pattern a wider canvas, so each cycle reveals fresh stripes scrolling in from the right.

    PitfallHow do I avoid a visible seam?

    Make the background larger than the viewport and ensure the start and end positions are compatible. Repeating patterns need a repeat unit that lands cleanly at the final background-position.

  5. Line 33background-repeat is controlled per layer. The overlay should not repeat, while the stripe layer can repeat along the pan axis.
  6. Line 36The edge fade is a separate overlay. It hides hard entrances at the viewport boundaries without changing the moving background math.
  7. Line 43Foreground text gets a stable translucent surface. Do not rely on every moving background frame to preserve text contrast.
    PitfallHow should text sit on a moving background?

    Put text on a stable surface or overlay and test the worst color position. Moving backgrounds change contrast over time, so bare text can become unreadable during part of the loop.

  8. Line 57Reduced motion freezes the scene at a representative midpoint. The reveal remains recognizable without continuous background travel.
    PitfallWhat should reduced motion do for moving backgrounds?

    Stop the background-position animation and hold a representative frame. Do not remove the foreground content or change the layout; only the background travel should disappear.

Other pitfalls

Is background-position animation performant?
It is paint work, so keep it for small hero panels or decorative strips. Avoid running many large moving backgrounds with blur, blend modes, or high-resolution images at the same time.

Notes

Overview

Layered background gradient drift behind a static foreground. Foreground content stays put; only the background layers move via transform + opacity (never layout properties like left or top) so the motion stays GPU-cheap.

When to use it

Reach for moving-background reveals on hero sections, page intros, and dashboard headers that want a sense of motion without committing to a full animation. Skip them on text-heavy reading surfaces — ambient peripheral motion is fatiguing during long sessions.

How it works

Stack two or three absolutely-positioned background layers behind the foreground content. Each layer holds a radial or conic gradient and animates transform: translate3d(x, y, 0) on a long loop (15–30 seconds). The translate3d form is critical — it forces compositor layer promotion so the animation runs on the GPU rather than the main thread. Use different durations and easing curves per layer so the layers never align at the same phase, which would produce a visible repeat. The foreground sits above with position: relative + a high z-index.

Production gotchas

Animating background-position instead of transform drops the animation to the CPU and causes jank under load. Don’t apply will-change: transform to every layer permanently — it pins them in GPU memory; add only when the animation is actively running, and remove on unmount or off-screen. Backgrounds with backdrop-filter stacked above the moving layers can produce ghosting on Safari iOS — either bake the blur into the layer image or accept the artifact below the iOS 17 floor.

Accessibility

Background drift in peripheral vision is exactly the kind of motion the prefers-reduced-motion: reduce media query targets. Pin all background layers at their starting positions (drop the animation) under reduced motion. The moving layers should remain decorative — do not rely on motion to communicate state. Verify foreground text contrast against the worst-case (darkest / lightest) frame of the background animation, not just the rest position.

References

Implementation depth

Moving backgrounds should animate transform on layers, not layout or gradient stops. The foreground content stays still while large background layers drift slowly enough to create atmosphere without stealing focus.

The worst frame matters more than the rest frame. Test foreground contrast when the brightest and darkest layers overlap, and turn the motion off under reduced motion while preserving the designed color field.