Scroll✨ Premium

Layered Parallax

Five absolute layers in a 900 px section, each on its own animation-timeline: scroll() path: background scale 1 → 1.3, shapes translateY 60 → −80 px with a −5° → +10° rotation, rings 40 → −60 px with +5° → −8°, title 20 → −20 px with opacity .7 → 1, dots 80 → −100 px. Pure CSS, no script, 620 px of travel.

CSSParallaxLayers

Updated

This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Scroll has 70 effects, including 12 free. Explore the category →

3 usage examples

Careers section of a design studio — parallax volumes on the left, job offers scrolling on the right — Layered Parallax example 1

① Careers section of a design studio — parallax volumes on the left, job offers scrolling on the right

WhenThe “Join the team” page of a digital studio: the scrolling area holds a two-column grid — on the left the effect's section, pinned with position: sticky over 360 px, with the title and the number of positions; on the right the intro, three offers and the perks. The visitor scrolls the offers, the five layers move alongside.
WhyFive speeds and two opposite rotations build real depth where Parallax (fx-0538) animates a single background plane; the stacked, antagonistic paths suggest creative energy without an illustration or a team photo, and take the studio's palette.
SettingsSold code untouched; section in position: sticky; top: 0 and 360 px tall, box at 360 px (the scene's height), warm palette (#1c1917 → #3a2a24, shapes #fb923c → #f43f5e, amber rings), enlarged shapes (58, 44 and 80 × 36 px), 118 and 70 px rings, 27 px left-aligned title. 300 px travel (660 px offers column); timeline bound to the scene's box — measured: shapes at translateY(−78px) and +9.8° at 99%.
Visual world of an electronic music label — bars of color like stacked tracks — Layered Parallax example 2

② Visual world of an electronic music label — bars of color like stacked tracks

WhenThe label's home page: a nav, then a 560 px section whose shapes became eight horizontal bars (120 × 14, 80 × 10 and 160 × 18 px, magenta, cyan and lime with a box-shadow glow) stacked in a column and offset, thin 170 and 96 px rings, the label's name in the center; below, the three latest releases.
WhyLayers moving at different speeds are the visual metaphor of the tracks of a recording, and the antagonistic rotations (+10° for the bars, −8° for the rings) twist that stack — none of the four other scroll parallaxes, fx-0538, fx-0548, fx-0557 and fx-0573, rotates. Against fx-0576 Parallax Layers, the catalog's other frame of stacked layers: there the word and the shapes sit in the first 280 pixels and leave the frame before 26% of the travel; here the bars are spread over the whole section and stay in the window up to 80% of the travel, the rings right to the end (measured).
Settings560 px section in a 360 px box (411 px travel with the 44 px nav and the list), layer 2 in flex-direction: column with a margin-left per bar (30 to 360 px), 38 px weight 900 title, .08 em tracking, magenta shadow; background #07060c → #14082a → #041a1e. Nothing pinned: the section scrolls as in the sold code; the title layer is raised 60 px (padding-bottom: 120px) so that title and subtitle fit in the 360 px at rest (title 259 px from the top, measured), and it leaves through the top at 60% of the travel.
Web cover of an annual report — the “Contents” sheet rises over a moving cover — Layered Parallax example 3

③ Web cover of an annual report — the “Contents” sheet rises over a moving cover

WhenThe digital annual report of an industrial group: an abstract cover in corporate colors (navy #08172e → #123a62, steel #9dbde0, orange #f28c28), a serif title, the financial year line; on scroll, a 240 px white sheet rises over the cover (180 px travel) and stops 120 px from the top — the title stays visible above a two-column table of contents.
WhyMulti-layer depth replaces a costly illustration while staying in the brand palette — fx-0573 Parallax Layers draws a frozen night landscape in a pinned viewport; fx-0592 is abstract, recolorable, and lets the section scroll on to the contents.
SettingsPinned section (position: sticky; top: 0, 360 px), sheet in margin-top: -60px and z-index: 3 (the sticky section forms its own stacking context: the title layer's z-index: 2 stays inside it), 66, 50 and 110 × 32 px shapes between .3 and .45 opacity, 150 and 86 px rings, Georgia 36 px title left-aligned 64 px from the top. 180 px travel: the shapes' 140 px of translation and 15° of rotation play out over 180 px of wheel, livelier than the sold code's 620 px.

How it works

Four levels in the sold HTML: the frame .demo-preview (centered flex, min-height: 300px, position: relative, #0a0a0f background), the box .sdc-scroll-container (100% × 280px, overflow-y: auto, scroll-behavior: smooth, a 4 px scrollbar via ::-webkit-scrollbar), the section .ss-parallax-content (900 px, position: relative, overflow: clip, 135° gradient #0f0f23 → #1a1a3e) and five .ss-parallax-layer in position: absolute; inset: 0. Layer 1: two radial glows, purple at 30% 40% and cyan at 70% 60%. Layer 2: eight .ss-parallax-shape in a centered flex-wrap — a 45 px square, a 35 px circle on even positions, a 55 × 30 pill on multiples of 3, opacities .5 / .4 / .35, purple → magenta and cyan → purple gradients. Layer 3: five .ss-parallax-ring, circles with a 2 px border, 60 px cyan and 40 px purple alternating. Layer 4 (z-index: 2): a 20.8 px weight 800 title and a 12 px subtitle in white at 60%, hard-coded texts (“Parallax Layers”, “Chaque couche se deplace a une vitesse differente”, French without accents). Layer 5: twelve .ss-parallax-dot of 8, 12 and 6 px. Below the box, span.sdc-scroll-hint “Scrollez ↓”, absolute 8 px from the bottom of the frame, 10.4 px, pulsing .3 ↔ .7 every 2 s. Empty JavaScript field. Travel: 900 − 280 = 620 px, identical at 1,264 and 374 px wide (measured).

Five @keyframes (ssPlx1 to ssPlx5), one per layer, each bound with animation-timeline: scroll(), animation-duration: auto, animation-range: normal, linear, animation-fill-mode: none. scroll() means scroll(nearest block): the timeline follows the nearest scroll container — the 280 px box — and progress is scrollTop ÷ 620. Measured in Chromium at 0 / 310 / 620 px: background scale 1 / 1.15 / 1.3 (the 1,264 × 900 px layer becomes 1,643 × 1,170, clipped by overflow: clip); shapes translateY 60 / −10 / −80 px and rotate −5° / 2.5° / 10°; rings 40 / −10 / −60 px and 5° / −1.5° / −8°; title 20 / 0 / −20 px and opacity .7 / .85 / 1; dots 80 / −10 / −100 px. At the end of the travel the animation reports finished yet stays applied (scale(1.3) read back at 620 px). For 620 px of wheel, five apparent speeds: the background 620 px (it grows, it does not rise), the title 660, the rings 720, the shapes 760, the dots 800. The two opposite rotations turn full-width layers around their center: the first shape, 280 px left of center, rises 209 px (140 of translation + 69 of rotation) while the eighth, on the right, rises only 73; for the rings it is the reverse, 69 px on the left and 133 on the right. This crossed twist builds the depth, more than the speeds do.

Shipped with overflow: hidden on the section, the effect was inert: hidden turns the section into a scroll container with zero travel, and scroll() was binding to it — measured on the original, timeline.source = .ss-parallax-content, currentTime null, five layers at transform: none from 0 to 620 px. overflow: clip clips identically without creating a scroll container: the timeline climbs to .sdc-scroll-container. That is the only line changed. No id, no script: pasted twice, each copy follows its own box (measured: the first at rest at translateY(80px), the second at the end of its travel at −100). The title, 449 px from the top of the section at rest, only enters the 280 px window at scrollTop 160 and leaves it at 440: centered mid-travel, invisible at rest.

Accessibility

  • prefers-reduced-motion not in the code: measured under emulation, nothing changes — scale 1.3, −100 px and 10° at the end of the travel, hint still pulsing. The motion follows the reader's gesture, but WCAG 2.3.3 also covers interaction-triggered animations: add @media (prefers-reduced-motion: reduce) { .ss-parallax-layer { animation: none } .sdc-scroll-hint { animation: none } .sdc-scroll-container { scroll-behavior: auto } }. The five layers then freeze in their base state (transform: none, opacity 1) — exactly what Firefox shows, everything stays readable.
  • Keyboard: measured on the sold code, Tab focuses the box without a tabindex in Chromium 151 as in Firefox 153; Arrow Down moves 40 px in Chromium (6.5% of the travel) and 51 in Firefox, Page Down reaches 285 and 303, End 620 — in Firefox the box scrolls from the keyboard but the layers stay flat (see Compatibility). WebKit 26.5 does not focus the box without tabindex="0": Tab stays on the page, arrows and End do nothing; with the attribute, 40, 280 and 620. Add it on .sdc-scroll-container, with an aria-label and a :focus-visible style. On touch, the gesture must start inside the 280 px; overscroll-behavior: contain keeps the page from being dragged along at the end.
  • Contrast: the 20.8 px white title reads 16.7 to 18.9:1 on the gradient, still 11:1 as it enters the window (layer at 78% opacity). The subtitle in white at 60% inside a .7 layer drops to 4.0:1 at rest — but it is not visible at rest: 4.7:1 as it enters (scrollTop 160), 5.3:1 at the center, 6.0:1 on the way out, a narrow pass for 12 px. Do not lower the starting opacity of ssPlx4 below .7 if the subtitle carries information. The hint at 30% reads 2.6:1: decorative, aria-hidden="true".
  • Screen readers: shapes, rings and dots are empty divs, nothing is announced; the title and subtitle are read in DOM order, the scroll position changes nothing. Put aria-hidden="true" on layers 1, 2, 3 and 5, and give layer 4 a real heading level (h2/h3) if the section opens a page. The two sold texts are French without accents: replace them.

Browser compatibility

CSS only: animation-timeline: scroll(), animation-duration: auto, animation-range, overflow: clip, flexbox, gradients, 2D transforms. Zero JavaScript, zero dependencies. Scroll-driven animations exist in Chrome and Edge 115+, Safari and iOS 26; Firefox runs them in no version at all. Measured in Playwright's Firefox on a blank page: CSS.supports('animation-timeline: scroll()') returns false, so does animation-duration: auto — the duration falls back to 0 s and, with animation-fill-mode: none, the five layers stay in their base state: transform: none, opacity 1, read back at 0, 155, 310, 465 and 620 px. A Firefox visitor sees a fixed 900 px section scrolling inside the 280 px box: glows, shapes, rings, title (visible from scrollTop 150 to about 450), dots — no parallax, nothing hidden, nothing out of frame, nothing overlapping. No @supports block in the code: the base state already is the flat fallback.

Chrome 115+✓ Full
Firefox (all versions)✓ No parallax: fixed section scrolling in the box, everything readable
Safari 26+✓ Full
Edge 115+✓ Full
Mobile iOS 26+✓ Full (the finger scrolls the box)
Android Chrome 115+✓ Full

Before Chrome/Edge 115 and Safari 26, same state as in Firefox: the animation-timeline and animation-duration: auto declarations are ignored, the layers stay flat and the section scrolls. overflow: clip is unknown before Chrome 90, Firefox 81 and Safari 16: the declaration is dropped, the section becomes overflow: visible, but nothing overflows since nothing is animated there (layers in inset: 0). Without the .demo-preview frame, the “Scrollez ↓” hint loses its anchor (position: absolute) and drops to the bottom of the page: remove it or keep a parent in position: relative.

The code

HTML structure to paste into your page (the CSS + JS come with Premium):

index.html — structure
<div class="demo-preview">
  <div class="sdc-scroll-container">
    <div class="ss-parallax-content">
      <div class="ss-parallax-layer ss-parallax-layer-1"></div>
      <div class="ss-parallax-layer ss-parallax-layer-2">
        <div class="ss-parallax-shape"></div> ×8
      </div>
      <div class="ss-parallax-layer ss-parallax-layer-3">
        <div class="ss-parallax-ring"></div> ×5
      </div>
      <div class="ss-parallax-layer ss-parallax-layer-4">
        <div>
          <div class="ss-parallax-title">…</div>
          <div class="ss-parallax-subtitle">…</div>
        </div>
      </div>
      <div class="ss-parallax-layer ss-parallax-layer-5">
        <div class="ss-parallax-dot"></div> ×12
      </div>
    </div>
  </div>
  <span class="sdc-scroll-hint">…</span>
</div>
Unlock the full code — €9.90/mo excl. tax

The full HTML + CSS + JS of Layered Parallax, ready to paste — plus all 811 effects. Or €299 once, lifetime access.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Travel (CSS — .ss-parallax-content { height: 900px }, .sdc-scroll-container { height: 280px }) 620 px Progress is scrollTop ÷ (section height − box height): every amplitude plays out over that travel. A 1,500 px section = 1,220 px of travel, a slower parallax; 400 px = 120 px, very lively (measured 180 px in scene 3). The box can take the height of your layout, or go away: without a box, scroll() follows the page.
Amplitudes (CSS — @keyframes ssPlx1 to ssPlx5) scale 1 → 1.3 · 60 → −80 px and −5° → 10° · 40 → −60 px and 5° → −8° · 20 → −20 px and .7 → 1 · 80 → −100 px Each layer has its two keyframes: change a value, its relative speed changes. Rotations turn the whole layer around its center: at 10°, a point 600 px from the center moves 104 px — on a wide section, reduce them to 3–5° or the edge shapes leave the frame (clipped by overflow: clip).
Background (CSS — .ss-parallax-content { background }, .ss-parallax-layer-1 { background }) 135° gradient #0f0f23 → #1a1a3e, two purple and cyan glows Layer 1 only carries the glows and grows from 1 to 1.3: at 1.3 it overflows by 190 px on each side, clipped. The .demo-preview frame stays #0a0a0f behind the box: match it to your page if it is light. The three scenes turn it into a warm, a neon and a navy palette.
Shapes, rings, dots (CSS — .ss-parallax-shape, -ring, -dot and their :nth-child) 8 shapes (45, 35 and 55 × 30 px), 5 rings (60/40 px), 12 dots (8/12/6 px) Three rules :nth-child(2n), (3n) and (5n) vary sizes and colors — the sixth shape is both 2n and 3n, the 3n rule written later wins. Add or remove divs, no count is imposed; the layout is a centered flex-wrap (gap 30, 20 and 20 px): flex-direction: column and a margin-left per element give stacked bars (scene 2).
Title (HTML + CSS — .ss-parallax-title, .ss-parallax-subtitle, .ss-parallax-layer-4) “Parallax Layers” 20.8 px weight 800, 12 px subtitle at 60%, centered 449 px from the top Layer 4 is a centered flex: at rest, the title sits outside the 280 px window, visible only between 160 and 440 px of scrolling. For a title readable from the start, align-items: flex-start; padding-top (scenes 1 and 3) or a shorter section. The .7 → 1 opacity lives in ssPlx4.
“Scrollez ↓” hint (HTML + CSS — .sdc-scroll-hint, @keyframes sdcHintPulse) 8 px from the bottom of the frame, 10.4 px, 30 → 70% every 2 s Absolute relative to the .demo-preview frame (position: relative): without the frame, it drops to the bottom of the page. Decorative and in French: remove the span, the keyframe goes with it.
Scrollbar and easing (CSS — .sdc-scroll-container) 4 px purple scrollbar (WebKit), scroll-behavior: smooth ::-webkit-scrollbar does not exist in Firefox (native bar); scrollbar-width: thin and scrollbar-color cover every engine. smooth only affects positions set by script or anchor: switch to auto if a button drives the box.
Timeline (CSS — animation-timeline: scroll(), animation-range: normal) nearest scroller, block axis, whole travel scroll(root) follows the page instead of the box; a named timeline (scroll-timeline: --plx y on the box, animation-timeline: --plx on the layers) binds the effect to a precise scroller even when an intermediate ancestor is overflow: auto or hidden — the trap that made the code inert before the repair. animation-range: 0 50% plays the whole amplitude over the first half of the travel.

FAQ

Because scroll() with no argument takes the nearest scroll container, and an overflow: hidden element is one — programmable, with zero travel. As originally sold, every layer therefore followed the section itself: inactive timeline, five layers at transform: none whatever the position, and the same rule kept the catalog demo still. overflow: clip clips identically but is not a scroll container: the timeline climbs to the 280 px box. To check on your side, in the console: document.querySelector('.ss-parallax-layer-5').getAnimations()[0].timeline.source must return .sdc-scroll-container (or html if you removed the box), and getComputedStyle(…).transform must change between the top and the bottom of the travel. If you wrap the effect in an overflow: hidden or auto block closer than the real scroller, the same trap closes again: switch that block to clip or name the timeline (see “Timeline” in the settings).
Same 280 px box, same 900 px section, same animation-timeline: scroll() mechanism — but fx-0548 animates vertical translations only: three radial glows (translateY 40 → −60 px), eight dots (20 → −40) and two centered texts, “PARALLAX” at 35 px and a caption (30 → −80 and 15 → −30, around a translate(-50%, -50%)). Four keyframes, four speeds, no rotation, no scale, no opacity. fx-0592 adds two dimensions: two rotations of opposite sign that twist the shapes layer and the rings layer in opposite directions (the left of the shapes rises 209 px, the right 73; the rings do the reverse), a background growing by 30% and a title emerging from .7 to 1 — on three families of elements (8 shapes in three variants, 5 rings, 12 dots) instead of glows and dots. Pick fx-0548 for a sober four-plane glide; fx-0592 when you want the twist and the background zoom, or typed volumes (bars, pills, rings) to recolor. The two twins in the catalog, Parallax Layers CSS and Parallax CSS (fx-0557, three blurred circles), carry the same section in overflow: hidden: the same console check applies to them.
Yes, in two measured ways. Remove the box (or set .sdc-scroll-container to height: auto; overflow: visible): scroll() climbs to the page's scroller and the progress runs over the whole document height — the layers also move while the section is off screen, which wastes the amplitude. Better replace scroll() with view() on the five layers: measured in an 800 px window with the section placed after 1,000 px of content, progress is 0% when the top of the section touches the bottom of the window, 47% when it fills the top (scale(1.14)), 100% when its bottom leaves through the top — 1,700 px of travel; outside that range the animation no longer applies (fill-mode: none), with no visible effect since the section is out of view. Keep overflow: clip on the section. The pinned variant of scenes 1 and 3 (position: sticky; top: 0 on the section, content rising over it) also works in the page: the section stops moving, only its layers glide.