Skip to content

Parallax

<quiet-parallax> stable since 5.3

Drifts a background layer as the element travels through the viewport, creating a scroll-driven parallax effect. Use a parallax for hero banners, full-bleed section breaks, and landing pages where the background should add a sense of depth as the reader scrolls.

Slot content into the component and it drifts as the element scrolls through the viewport. The effect is built entirely on CSS scroll-driven animations , so it runs off the main thread with no scroll listeners and nothing to wire up. Scroll the page to watch it move.

Stylized sunset over layered hills
Parallax Scroll to watch the scene drift
<quiet-parallax id="parallax__overview">
  <img slot="background" style="--offset: 0.18;" src="/assets/images/parallax/overview.svg" alt="Stylized sunset over layered hills" />
  <img slot="background" style="--offset: 0.32;" src="/assets/images/parallax/layer-ridge-3.svg" alt="" />

  <div class="hero">
    <strong>Parallax</strong>
    <span>Scroll to watch the scene drift</span>
  </div>
</quiet-parallax>

<style>
  #parallax__overview {
    height: 360px;
    border-radius: var(--quiet-border-radius-md);

    /* Stack the layers so the near hill drifts faster than the scene behind it */
    img {
      position: absolute;
      inset: 0;
    }

    .hero {
      display: grid;
      justify-items: center;
      gap: 0.5rem;
      text-align: center;
      color: white;
    }

    strong {
      font-size: clamp(2rem, 6vw, 3.5rem);
      font-weight: 600;
    }

    span {
      font-size: 0.8rem;
      letter-spacing: 0.15em;
      text-transform: uppercase;
      opacity: 0.85;
    }
  }
</style>

Scroll-driven animations are supported in Chrome, Edge, and Safari, but Firefox keeps them behind a flag . Unsupportive browsers and users who prefer reduced motion will see the image, but not the effect.

Examples Jump to heading

Providing content Jump to heading

Slot an image, video, or any other visual element into the background slot. It's sized to fill the box for you, so a bare element works with no wrapper.

The parallax element has no intrinsic size of its own, so it ships with a small min-height to keep a bare one visible. Set a height or min-height to size it to your layout, since the drift needs room to travel.

The background slot is scaled and clipped, so reserve it for content that can fill the box and take some cropping. Anything that must stay crisp or sit still belongs in the default slot instead. See the Overlaying Content example for details.

Calico kitten lying down against a warm yellow background
<quiet-parallax id="parallax__background">
  <img
    slot="background"
    src="https://images.unsplash.com/photo-1536500152107-01ab1422f932?q=80&w=2070&auto=format&fit=crop"
    alt="Calico kitten lying down against a warm yellow background"
  />
</quiet-parallax>

<style>
  #parallax__background {
    height: 320px;
    border-radius: var(--quiet-border-radius-md);
  }
</style>

Adjusting the intensity Jump to heading

Set the --offset custom property to control how far the background drifts, as a fraction of its own size, so 0.2 is 20%. The background scales up to cover the extra travel, so any value stays gap-free.

Stylized teal dunes at dusk Stylized magenta dunes at dusk
<quiet-parallax id="parallax__subtle" style="--offset: 0.06;">
  <img slot="background" src="/assets/images/parallax/dunes-cool.svg" alt="Stylized teal dunes at dusk" />
</quiet-parallax>

<quiet-parallax id="parallax__dramatic" style="--offset: 0.38;">
  <img slot="background" src="/assets/images/parallax/dunes-warm.svg" alt="Stylized magenta dunes at dusk" />
</quiet-parallax>

<style>
  #parallax__subtle,
  #parallax__dramatic {
    height: 280px;
    border-radius: var(--quiet-border-radius-md);
  }
</style>

Overlaying content Jump to heading

Put anything that must stay crisp, like a heading, a paragraph, or a button, in the default slot. It sits still and sharp above the drifting background, centered by default, and stays interactive so links and buttons work as usual.

Stylized purple dusk landscape

Stays crisp

The background drifts. This text doesn't.

Get started
<quiet-parallax id="parallax__overlay" style="--offset: 0.24;">
  <img slot="background" src="/assets/images/parallax/overlay.svg" alt="Stylized purple dusk landscape" />

  <div class="hero">
    <h3>Stays crisp</h3>
    <p>The background drifts. This text doesn't.</p>
    <quiet-button variant="primary" pill>Get started</quiet-button>
  </div>
</quiet-parallax>

<style>
  #parallax__overlay {
    height: 360px;
    border-radius: var(--quiet-border-radius-md);

    .hero {
      display: grid;
      justify-items: center;
      gap: 1rem;
      padding: 2rem;
      text-align: center;
      color: white;
    }

    h3 {
      margin: 0;
      font-size: clamp(1.75rem, 5vw, 2.75rem);
    }

    p {
      margin: 0;
      color: rgb(255 255 255 / 0.85);
    }
  }
</style>

Reversing the drift Jump to heading

Set --direction to -1 to flip which way the background moves relative to the scroll.

Stylized green hills beneath a pale sun
<quiet-parallax id="parallax__reverse" style="--offset: 0.26; --direction: -1;">
  <img slot="background" src="/assets/images/parallax/reverse.svg" alt="Stylized green hills beneath a pale sun" />
</quiet-parallax>

<style>
  #parallax__reverse {
    height: 320px;
    border-radius: var(--quiet-border-radius-md);
  }
</style>

Adjusting the range Jump to heading

--offset sets how far the background drifts, but --range sets when it drifts. The travel covers the same distance either way, just spread over a longer or shorter stretch of the element's scroll through the viewport.

By default, cover spreads the drift across the element's entire scroll through the viewport, so the background keeps moving the whole time it's on screen. The other values pack it into a shorter window:

  • cover (default): the whole time the element overlaps the viewport.
  • contain: only while it's fully on screen, resting as it enters and leaves.
  • entry: only as it scrolls into view, then it rests.
  • exit: rests until it starts to leave, then drifts out.

Any animation-range value works, so you can also pass explicit lengths or percentages. Pick a value below and scroll to compare each one.

Stylized purple dusk landscape cover contain entry exit
<div id="parallax__range">
  <quiet-parallax style="--offset: 0.28;">
    <img slot="background" src="/assets/images/parallax/overlay.svg" alt="Stylized purple dusk landscape" />
  </quiet-parallax>

  <quiet-radio label="Range" name="range" value="cover" class="quiet-vh-label">
    <quiet-radio-item value="cover">cover</quiet-radio-item>
    <quiet-radio-item value="contain">contain</quiet-radio-item>
    <quiet-radio-item value="entry">entry</quiet-radio-item>
    <quiet-radio-item value="exit">exit</quiet-radio-item>
  </quiet-radio>
</div>

<script type="module">
  const container = document.getElementById('parallax__range');
  const parallax = container.querySelector('quiet-parallax');
  const range = container.querySelector('quiet-radio');

  range.addEventListener('quiet-change', () => {
    parallax.style.setProperty('--range', range.value);
  });
</script>

<style>
  #parallax__range {
    display: flex;
    flex-direction: column;
    gap: 1.5rem;

    quiet-parallax {
      height: 280px;
      border-radius: var(--quiet-border-radius-md);
    }
  }
</style>

Creating depth with layers Jump to heading

Each element in the background slot can set its own --offset, since the value inherits. It's a unitless fraction of the layer's own size, so 0.04 drifts only slightly while 0.34 moves much farther. Give distant layers a small offset and nearer ones a larger one, and the gap in their travel reads as depth. A lone background uses the default of 0.2.

<quiet-parallax id="parallax__layers">
  <img slot="background" style="--offset: 0.04;" src="/assets/images/parallax/layer-sky.svg" alt="" />
  <img slot="background" style="--offset: 0.07;" src="/assets/images/parallax/layer-sun.svg" alt="" />
  <img slot="background" style="--offset: 0.12;" src="/assets/images/parallax/layer-ridge-1.svg" alt="" />
  <img slot="background" style="--offset: 0.2;" src="/assets/images/parallax/layer-ridge-2.svg" alt="" />
  <img slot="background" style="--offset: 0.34;" src="/assets/images/parallax/layer-ridge-3.svg" alt="" />
</quiet-parallax>

<style>
  #parallax__layers {
    height: 360px;
    border-radius: var(--quiet-border-radius-md);

    /* Stack the layers so they share the box instead of running top to bottom */
    img {
      position: absolute;
      inset: 0;
    }
  }
</style>

Scrolling horizontally Jump to heading

Inside a horizontal scroller, set orientation="horizontal" to make the parallax effect drift sideways.

Stylized coastal scene at dusk Stylized magenta scene at dusk Stylized green hills beneath a pale sun
<div class="row" id="parallax__horizontal">
  <quiet-parallax orientation="horizontal" style="--offset: 0.28;">
    <img slot="background" src="/assets/images/parallax/horizontal-1.svg" alt="Stylized coastal scene at dusk" />
  </quiet-parallax>
  <quiet-parallax orientation="horizontal" style="--offset: 0.28;">
    <img slot="background" src="/assets/images/parallax/horizontal-2.svg" alt="Stylized magenta scene at dusk" />
  </quiet-parallax>
  <quiet-parallax orientation="horizontal" style="--offset: 0.28;">
    <img slot="background" src="/assets/images/parallax/horizontal-3.svg" alt="Stylized green hills beneath a pale sun" />
  </quiet-parallax>
</div>

<style>
  #parallax__horizontal {
    display: flex;
    gap: 1rem;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x proximity;

    quiet-parallax {
      flex: 0 0 76%;
      height: 300px;
      border-radius: var(--quiet-border-radius-md);
      scroll-snap-align: center;
    }
  }
</style>

For a diagonal drift, set both --x and --y. The orientation attribute simply presets this pair (vertical is --x: 0; --y: 1, horizontal is --x: 1; --y: 0), but you can override them via CSS.

Drifting text Jump to heading

The background doesn't have to be a picture. Drop large display type into the background slot and the words drift too. Keep the offset subtle so the text remains legible.

words in motion the background layer, set adrift
<quiet-parallax id="parallax__text" style="--offset: 0.12;">
  <div slot="background">
    <strong>words in motion</strong>
    <span>the background layer, set adrift</span>
  </div>
</quiet-parallax>

<style>
  #parallax__text {
    height: 320px;
    border-radius: var(--quiet-border-radius-md);
    background: linear-gradient(135deg, var(--quiet-primary-900), var(--quiet-primary-950));

    [slot='background'] {
      display: grid;
      place-content: center;
      padding: 2rem;
      text-align: center;
    }

    strong {
      font-size: clamp(2.4rem, 8vw, 4.5rem);
      font-weight: 600;
      line-height: 1.05;
      /* Tint the drifting type with the primary scale instead of a flat color */
      background: linear-gradient(180deg, var(--quiet-primary-100), var(--quiet-primary-300));
      background-clip: text;
      -webkit-background-clip: text;
      color: transparent;
    }

    span {
      margin-block-start: 1rem;
      font-size: 0.8rem;
      letter-spacing: 0.2em;
      text-transform: uppercase;
      color: var(--quiet-primary-300);
    }
  }
</style>

Syncing to scroll progress Jump to heading

The element exposes a read-only --parallax-progress custom property that climbs from 0 to 1 as the box travels through the viewport, riding the same timeline as the drift. Because it's a CSS variable, you can drive your own effects from it inside the overlay without using JavaScript.

Here a heading drifts upward and a bar fills as you scroll past. This effect pairs nicely with --range: contain so the progress remains visible from start to end.

Stylized purple landscape at dusk
Scroll-linked
<quiet-parallax id="parallax__progress" style="--offset: 0.26; --range: contain;">
  <img slot="background" src="/assets/images/parallax/progress.svg" alt="Stylized purple landscape at dusk" />

  <div class="hero">
    <strong>Scroll-linked</strong>
    <div class="bar"><span></span></div>
  </div>
</quiet-parallax>

<style>
  #parallax__progress {
    height: 360px;
    border-radius: var(--quiet-border-radius-md);

    .hero {
      display: grid;
      gap: 1.25rem;
      justify-items: center;
      color: white;
      /* a no-op at progress 0, so it sits normally where the effect isn't supported */
      translate: 0 calc((1 - var(--parallax-progress)) * 0.75rem);
    }

    strong {
      font-size: clamp(1.75rem, 5vw, 2.75rem);
      font-weight: 600;
    }

    .bar {
      width: min(60%, 240px);
      height: 6px;
      border-radius: 999px;
      background: rgb(255 255 255 / 0.25);
      overflow: hidden;
    }

    .bar span {
      display: block;
      height: 100%;
      border-radius: inherit;
      background: white;
      transform-origin: left;
      /* fill from empty to full as progress goes 0 → 1 — composited, no JS, no reflow */
      transform: scaleX(var(--parallax-progress));
    }
  }

  /* The bar only means something while progress can move, so hide it where it can't. */
  @supports not (animation-timeline: view()) {
    #parallax__progress .bar {
      display: none;
    }
  }
</style>

--parallax-progress is a read-only custom property. It's set to 0 in browsers without scroll-driven animation support and for users who prefer reduced motion.

Using as a page section Jump to heading

A parallax also works as a full-bleed section in the middle of a page. Place one between blocks of regular content and it drifts as the reader scrolls past. The example below uses a browser frame to stand in for the page, so scroll inside it to watch the band travel through.

Above the band

Ordinary content scrolls normally. Keep reading and the band below drifts as it passes.

The band sits in the regular flow, so nothing is pinned and nothing is wired up.

Green-eyed brown tabby cat looking up at the camera on a tiled floor
Full-bleed band

Below the band

More content continues the flow. The band only moves while it overlaps the viewport.

Keep scrolling and it settles back to rest as it leaves.

<quiet-browser-frame label="example.com" flush style="max-height: 420px;">
  <quiet-icon slot="icon" name="cat"></quiet-icon>
  <div id="parallax__section">
    <div class="prose">
      <h3>Above the band</h3>
      <p>Ordinary content scrolls normally. Keep reading and the band below drifts as it passes.</p>
      <p>The band sits in the regular flow, so nothing is pinned and nothing is wired up.</p>
    </div>

    <quiet-parallax style="--offset: 0.3;">
      <img
        slot="background"
        src="https://images.unsplash.com/photo-1532597306305-c91ad16429f0?q=80&w=2070&auto=format&fit=crop"
        alt="Green-eyed brown tabby cat looking up at the camera on a tiled floor"
      />
      <div class="hero"><strong>Full-bleed band</strong></div>
    </quiet-parallax>

    <div class="prose">
      <h3>Below the band</h3>
      <p>More content continues the flow. The band only moves while it overlaps the viewport.</p>
      <p>Keep scrolling and it settles back to rest as it leaves.</p>
    </div>
  </div>
</quiet-browser-frame>

<style>
  #parallax__section {
    .prose {
      max-width: 60ch;
      margin-inline: auto;
      padding: 3rem 2rem;

      p {
        color: var(--quiet-text-muted);
      }
    }

    quiet-parallax {
      height: 300px;

      .hero {
        color: white;
        font-size: clamp(1.75rem, 5vw, 2.75rem);
        font-weight: 600;
        text-shadow: 0 1px 12px rgb(0 0 0 / 0.5); /* keep the overlay legible over the photo */
      }
    }
  }
</style>

API Jump to heading

Importing Jump to heading

The autoloader is the recommended way to import components but, if you prefer to do it manually, the following code snippets will be helpful.

CDN Self-hosted

To manually import <quiet-parallax> from the CDN, use the following code.

import 'https://cdn.quietui.org/v5.3.1/components/parallax/parallax.js';

To manually import <quiet-parallax> from a self-hosted distribution, use the following code. Remember to replace /path/to/quiet with the appropriate local path.

import '/path/to/quiet/components/parallax/parallax.js';

Slots Jump to heading

Parallax supports the following slots. Learn more about using slots

Name Description
background The background layer that drifts as the element scrolls through the viewport. It's sized to fill the box automatically, so you can drop in a bare image, video, or SVG with no wrapper. Each child rides the same timeline, so giving each its own --offset produces a layered sense of depth. Use fill-the-box content or large display type. It's scaled up and clipped, so it's not meant for content that must stay pixel-crisp.
(default) Static overlay content (headings, captions, calls to action) that sits above the background and does not drift. This is the home for anything interactive or pixel-crisp.

Properties Jump to heading

Parallax has the following properties that can be set with corresponding attributes. In many cases, the attribute's name is the same as the property's name. If an attribute is different, it will be displayed after the property. Learn more about attributes and properties

Property Description Reflects Type Default
orientation The axis the background drifts along. Sets sensible defaults for --timeline-axis, --x, and --y so a horizontal scroller works without wiring those up by hand. Override any of those custom properties to fine-tune or to produce a diagonal drift. 'vertical' | 'horizontal' 'vertical'

CSS custom properties Jump to heading

Parallax supports the following CSS custom properties. You can style them like any other CSS property. Learn more about CSS custom properties

Name Description Default
--offset Intensity. Travel as a unitless fraction of the element's own size, each way, so 0.2 is 20%. The scale-to-cover derives from this, so any value stays gap-free. 0.2
--direction Set -1 to reverse the drift. 1
--x Horizontal travel multiplier. 0 off, 1 full. Set by orientation; override it (alongside --y) for diagonal travel. 0
--y Vertical travel multiplier. The default x:0, y:1 is the classic top-to-bottom drift. Set by orientation; override it (alongside --x) for diagonal travel. 1
--timeline-axis Which scroll direction the timeline reads. block for a vertical scroller, inline for a horizontal one. Set by orientation; override only for unusual scroller setups. block
--range How much of the element's trip across the viewport the drift covers. cover runs the whole time the box overlaps the viewport; contain only while it's fully inside; entry/exit confine it to the moments it's coming in or leaving. Accepts any CSS animation-range value. cover
--parallax-progress Read-only. A unitless 0–1 value that tracks the element's progress through the viewport on the same timeline as the drift, inherited onto both slots and the overlay. Consume it from your own CSS (e.g. inside a calc()) to sync effects to the scroll. Stays 0 where scroll-driven when animations are unavailable, and remains at 0 with reduced motion. 0

CSS parts Jump to heading

Parallax exposes internal elements that can be styled with CSS using the selectors shown below. Learn more about CSS parts

Name Description CSS selector
overlay The container that wraps the static overlay (default slot) content. Centered over the background by default; restyle this part to reposition it. ::part(overlay)
Search this website Toggle dark mode View the code on GitHub Follow @quietui.org on Bluesky Follow @quiet_ui on X

    No results found