For years, getting a background to trail behind the text meant a JavaScript scroll listener and a transform rewritten on every frame. CSS can now do that job by itself, and the browser handles the motion. That’s CSS parallax: page layers moving at different speeds while you scroll, with no scroll listener anywhere.

Designers mostly use it on hero sections and layered landing pages, where a slower background reads as distance.

Fixed backgrounds and sticky layers are the simple ways to get there, and perspective with translateZ is the one that gives real depth. The newest method, scroll-driven animations, ties a keyframe animation to scroll position instead of time. Chrome for Developers documents it in Chrome 115, and WebKit lists it in Safari 26.0.

What Is CSS Parallax and Which Methods Create It?

See the Pen
Pure CSS parallax demo
by Keith Clark (@keithclark)
on CodePen.

Depth comes from the speed gap. A background layer travels slower than the text in front of it, and the eye reads that gap as distance, the same illusion designers know as parallax scrolling.

The older route ran on scripts. A JavaScript scroll listener read the position and rewrote transform values on every layer. CSS methods hand that work to the browser’s own scrolling and compositing.

MethodMechanismMain failure mode
background-attachment: fixedBackground pinned to the viewportPartial support on iOS Safari
Perspective with translateZ3D depth inside a scroll containeroverflow: hidden on a group flattens the 3D scene
Scroll-driven animationsKeyframes tied to scroll positionNot Baseline on MDN
Sticky with transformLayer pinned inside a tall wrapperAncestor overflow stops the sticking

None of the four covers every case. Fixed backgrounds take the least code. Perspective is the one with real layered depth. Scroll-driven animations have the cleanest markup, and sticky is handy when the effect has to stay inside its own section.

How to Build Parallax with background-attachment: fixed

See the Pen
Parallax Background
by Ravi Dhiman (@ravid7000)
on CodePen.

Add background-attachment: fixed to an element with a background image, and the image stays pinned to the viewport while content scrolls over it. One declaration produces the effect.

The usual setup is a hero image behind a headline, with normal content scrolling past it.

.parallax {
background-image: url("hero.jpg");
background-attachment: fixed;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
min-height: 500px;
}

Alternating parallax bands with plain “buffer” sections is a design choice, not a requirement (LogRocket describes this banded layout). min-height sets how much of the picture is visible per band. And because cover sizes the image against the viewport when the attachment is fixed, the source file needs to be at least screen-sized.

Can I use lists iOS Safari as partial support in every version from 5.0 through 26.6.

WebKit’s bug tracker (bug 234779) spells out what partial means. The background scrolls with the page but is still cropped as if it were fixed. The same report notes that @supports claims the property is supported, so a feature query cannot be relied on to gate it.

Turn the effect off with media queries and fall back to background-attachment: scroll. A width breakpoint misses large tablets, so (pointer: coarse) targets touch screens more directly.

The upside is how little it takes. One declaration, no transforms, and it works on any element that has a background, with no scroll container to restructure.

The downsides are real, though. It’s broken on iOS Safari. You also get one layer only, always at zero speed relative to the viewport, and background images carry no alt text.

How Does the Perspective and translateZ Method Work?

See the Pen
Pure CSS Parallax Effect (Depth of field)
by FlyC (@FlyC)
on CodePen.

Keith Clark described this technique in his August 2014 article on pure CSS parallax websites. Layers render at different depths inside one 3D scene, so scrolling moves distant layers slower than near ones. It needs a scroll container with a perspective value and children offset on the Z axis, and the pushed-back layers get a scale() correction so they keep their original size.

The W3C CSS Transforms Module Level 2 defines the properties involved, which are perspective, perspective-origin, transform-style and translateZ.

Setting Up the Scroll Container

See the Pen
Parallax website using only CSS
by Kim (@Kimberly-0)
on CodePen.

The scroll container, not the page, supplies the 3D viewport. Per Clark, giving an element a height plus the CSS perspective property creates a fixed 3D viewport centered on it.

  1. Create the container with height: 100vh, overflow-x: hidden, overflow-y: auto and perspective: 1px.
  2. Add a group wrapper per section with position: relative and transform-style: preserve-3d.
  3. Place each layer inside its group with position: absolute and all four offsets at 0.
  4. Give the base layer translateZ(0) so it scrolls at normal speed.
  5. Give the background layer a negative translateZ, such as -1px, plus the matching scale value.
  6. Keep the text on the base layer.

Calculating the Scale Correction

Pushing a layer back shrinks it, so it needs scale() to render at its original size. Clark’s formula is scale = 1 + (translateZ × -1) / perspective.

translateZ (perspective 300px)ScaleScroll speed vs page
90px0.71.43x (faster)
011x
-300px20.5x
-600px30.33x

The translateZ values come from Clark’s demo. The speed column follows from the formula, since a layer’s scroll speed is the inverse of its scale.

perspective-origin moves the vanishing point. Clark’s article shows perspective-origin-x: 100% to shift it to the right edge.

Pros and Cons of the Perspective Method

You get real multi-layer depth with no scripts, and each layer’s speed comes down to one number.

The catch is that the page scrolls inside a div, so window scroll position stays at 0. overflow: hidden on a group breaks the effect, per Clark. Scaled layers also grow, so background images need oversized source files.

How to Create Parallax with Scroll-Driven Animations

Scroll-driven animations link a keyframe animation to scroll progress through animation-timeline, so the animation advances with the scrollbar instead of the clock. For parallax, that means a transform animation on a background layer.

@keyframes drift {
from { transform: translateY(-15%); }
to   { transform: translateY(15%); }
}

.parallax-bg {
animation: drift linear both;
animation-timeline: view();
}

The animation is an ordinary set of CSS keyframes. Linear easing keeps the motion proportional to scroll distance.

scroll() gives you a scroll progress timeline, which tracks how far a scroll container has scrolled. MDN lists nearest, root and self as scroller values. view() is a view progress timeline that tracks an element’s passage through the scrollport, and it suits section-level parallax because each section animates on its own schedule.

animation-range limits where on the timeline the animation runs, using named ranges such as cover, contain, entry and exit. Declare animation-timeline and animation-range after the animation shorthand, because the shorthand resets the longhands it leaves out to their initial values (animation-timeline returns to auto, per MDN).

Named timelines come from scroll-timeline-name or view-timeline-name. By default, a named timeline can only be used by descendants of the element that declares it (Chrome for Developers), and timeline-scope extends that reach.

Wrap the rules in @supports (animation-timeline: scroll()). Browsers without support keep the static layer, which is a fine failure for decoration.

A polyfill exists, but it moves the work back to JavaScript and removes the main reason to choose this method.

Chrome 115 added the CSS properties and the ScrollTimeline and ViewTimeline JavaScript classes (Chrome for Developers guide), and Safari 26.0 adds scroll-driven animations (WebKit release notes). There are 2 timeline types, scroll progress and view progress. The guide names scroll-timeline-name, view-timeline-name, animation-timeline and animation-range as the properties involved. MDN does not mark scroll() or animation-range as Baseline at the time of writing (October 2026).

The markup stays clean, it runs on normal window scroll, and per-layer speed comes from the keyframes. What you give up is browser reach, so a static fallback is needed, and named timelines come with scope rules.

How Do Position Sticky and Transform Create Layered Scroll Effects?

See the Pen
CSS Sticky Parallax Sections
by Ryan Mulligan (@hexagoncircle)
on CodePen.

A sticky layer inside a tall wrapper stays put while content scrolls over it, which is parallax at zero speed. Add a transform driven by a scroll-driven animation and the layer drifts at a fraction of page speed.

That dependency matters. Where scroll-driven animations are unsupported, the layer stays pinned and never drifts.

The gain over a fixed background is containment. A sticky layer stops at the bottom edge of its containing block, so the effect ends with its wrapper.

The usual way this breaks is ancestor overflow. MDN positions a sticky element against its nearest scrolling ancestor, so overflow: hidden, auto or scroll on any ancestor makes it the reference, and the layer stops sticking. overflow: clip fixes that, because it clips content without creating a scroll container, as Ben Frain’s write-up of MDN’s definition points out.

Size is the other snag. A sticky element larger than its scroll container cannot hold its position, and once you scroll far enough, the top portion that had stuck scrolls away (Frontend Masters).

Pick sticky when the layer should be a real element, such as an SVG or an img with alt text, rather than a CSS background.

Which CSS Parallax Method Should You Choose?

See the Pen
CSS-Only Parallax Effect
by Yago Estévez (@yagoestevez)
on CodePen.

Perspective suits layered depth, and scroll-driven animations suit a single drifting layer on current browsers. A JavaScript library only makes sense when CSS can’t express the motion, and most pages never reach that stage.

For one background image and a desktop audience, background-attachment: fixed is enough. Three or more layers with real depth point to perspective with translateZ. One or two drifting layers on current browsers fit scroll-driven animations behind @supports, and sticky positioning is the pick for a layer that must end with its section.

Skip the fixed background whenever phone traffic matters. iOS Safari gives it no effect in any version, even though it is the shortest of the four to write.

A library earns its weight when the effect needs scrubbed timelines, pinned sections, or sequencing across many elements. The other trigger is browser reach. Scroll-driven animations still carry a cross-browser compatibility gap, and a script closes it at the cost of weight.

LibraryWhat it doesNote
GSAP ScrollTriggerScrubbed, pinned, sequenced scroll animationFree, commercial use included (current README)
Rellax.jsPer-element parallax speed through a data attributeRoughly 2.5 KB gzipped, v1.12.1 (Bundlephobia)
JarallaxParallax for background images and video (YouTube, Vimeo, self-hosted)disableParallax option for mobile
Locomotive ScrollSmooth scrolling, in-view detection, data-scroll-speed parallaxVersion 5 is built on Lenis
LenisSmooth scrolling layerOften paired with ScrollTrigger

Licensing advice conflicts between sources. Older GreenSock forum answers say sites that charge multiple customers need a Business Green membership, while the current GSAP README says the whole toolset, plugins included, is free for commercial use since Webflow’s acquisition.

Read the license text for the exact version before shipping a paid product.

CSS alone is enough when the effect is decoration, not choreography, and a static fallback is acceptable on older browsers. It also wins when script weight counts against a performance budget.

A library makes sense when scroll position must drive timelines, pins or sequences, or when browsers below the current generation are a hard requirement. Tuning speed per element at runtime is another reason.

The price of a library is script weight plus main-thread work on every scroll frame. Build the static mobile layout first, as in mobile-first design, and add either approach on top.

Does CSS Parallax Hurt Page Performance?

Not when the motion stays on transform. Transform-based CSS parallax stays cheap, and the cost rises with layer count and with any property that forces paint or layout.

Compositor-Friendly Properties

web.dev’s rendering guidance names 2 properties that the compositor can animate without layout or paint, transform and opacity.

translateY, translateZ and scale all count as transform, so the perspective and scroll-driven methods stay inside that budget. Animating background-position, top or margin does not.

Can I use adds a note on the fixed-background method. Most mobile devices show a delay in updating the background position after scrolling.

Layer and Memory Cost

MDN warns that excessive will-change use causes excessive memory use, and advises against adding it to pages that already perform well.

Each compositor layer takes memory too, and on low-memory devices the cost can outweigh the gain (web.dev). For compositing time during scrolling, web.dev suggests aiming for roughly 4-5ms.

Each translateZ layer in the perspective method is typically promoted to its own compositor layer. Count layers, not sections.

Layout Shift and Core Web Vitals

When a parallax section has no reserved height, the image loads, the section grows, and everything below it jumps. That’s CLS, and web.dev sets the good threshold at 0.1.

Set min-height or aspect-ratio on every parallax container and declare width and height on the layer images. The hero image should load eagerly, since it is usually the LCP element.

Measuring the Cost

Open the Layers view in Chrome DevTools to count layers and see why each one exists. Then record a scroll in the Performance panel to catch layout shifts, and run Lighthouse for a lab CLS reading.

How to Make CSS Parallax Accessible

Run the motion only under prefers-reduced-motion: no-preference, serve a static layout to everyone else, and treat WCAG SC 2.3.3 as the standard to meet. The fallback is part of the build, not a patch.

Motion triggers real symptoms. Vestibular reactions can include dizziness and nausea, and web.dev also lists migraine headaches, some serious enough to need bed rest.

Motion is one barrier among many in web accessibility work, but it is the one parallax creates by design.

The W3C’s Understanding document for SC 2.3.3 names parallax scrolling as often non-essential animation. The criterion sits at Level AAA, arrived with WCAG 2.1, and carries into WCAG 2.2.

SC 2.2.2 (Pause, Stop, Hide) is the other motion criterion. It covers animation the page starts on its own that runs longer than five seconds, so scroll-driven parallax falls under 2.3.3 instead.

  1. Write the base CSS with no motion, so the static layout is the default.
  2. Put the parallax rules inside @media (prefers-reduced-motion: no-preference), so motion is opt-in.
  3. For scroll-driven animations, nest the @supports test inside that same media query.
  4. For the perspective method, set perspective: none on the container and transform: none on the layers under (prefers-reduced-motion: reduce).
  5. For libraries, check window.matchMedia(“(prefers-reduced-motion: reduce)”) and skip initialization, or call destroy() where one exists (Rellax resets elements to their original positions).
  6. Test the reduced setting in DevTools and on a real device.
@media (prefers-reduced-motion: no-preference) {
@supports (animation-timeline: scroll()) {
.parallax-bg {
animation: drift linear both;
animation-timeline: view();
}
}
}

Visitors turn the preference on in their OS settings, and the path differs by system.

  • macOS: System Settings, Accessibility, Display, Reduce motion
  • iOS and iPadOS: Settings, Accessibility, Motion, Reduce Motion
  • Windows 11: Settings, Accessibility, Visual effects, Animation effects
  • Windows 10: Settings, Ease of Access, Display, Show animations in Windows
  • Android: Accessibility, Remove animations

In Chrome DevTools, the command menu runs Emulate CSS prefers-reduced-motion: reduce, which tests the CSS layer without touching OS settings.

A static layout is the safest fallback. Craft CMS’s design guidance offers a fade-in as a polished alternative.

When Does CSS Parallax Break or Not Apply?

Drop it on pages built for reading or form-filling, and anywhere layout height is unstable. In those cases the effect adds cost and takes something away.

Long articles, documentation and legal text gain nothing from drifting backgrounds, so use a static hero there. On forms and checkout, motion behind inputs competes with the task. A flat background works better.

Text over a moving image is a problem because contrast changes frame by frame, and a scrim behind the text helps. Full-height sections on phones overshoot the visible screen with 100vh, so use svh. Every layer costs memory on low-memory devices, which means cutting the layer count. Reduced-motion users get the static layout, no exceptions.

The mobile viewport changes as toolbars collapse, which is why web.dev calls 100vh too tall on load.

svh sizes to the small viewport (toolbars shown), lvh to the large one, and dvh follows the toolbar as it moves. dvh does not update at 60fps (web.dev), and a dvh-sized element reflows during scroll, so svh suits always-on hero sections.

A caption that clears color contrast over the sky in a photo can fail over the dark ground a few hundred pixels later. Test the worst frame, not the first one.

A scrim layer with fixed opacity behind the text removes the variable.

CSS parallax examples

CSS Parallax Magic by Kimgrae

See the Pen
Untitled
by kimgrae (@kimgrae)
on CodePen.

Parallax Dream of UGG

See the Pen
Parallax World of UGG
by Andrew (@designbyremedy)
on CodePen.

All-CSS UI by Andrej Sharapov

See the Pen
Pure CSS: Parallax (Demix website)
by Andrej Sharapov (@andrejsharapov)
on CodePen.

Canvas Parallax Horizon by Jack Rugile

See the Pen
Canvas Parallax Skyline
by Jack Rugile (@jackrugile)
on CodePen.

Responsive Parallax Panorama

See the Pen
Castles, responsive parallax landscape
by Karim Maaloul (@Yakudoo)
on CodePen.

Artsy CSS Parallax Feels

See the Pen
Creative Parallax Scrolling Effect
by Stack Findover (@stack-findover)
on CodePen.

Parallax Hero Animation

See the Pen
Parallax animation hero
by Landrik (@landrik)
on CodePen.

Magdiellop 216 with CSS

See the Pen
Magdiellop 216 recreated with CSS
by Guilmain Dorian (@Craaftx)
on CodePen.

Moving Parallax Magic

See the Pen
Parallax scroll animation
by isladjan (@isladjan)
on CodePen.

Starry Parallax by Aldwin

See the Pen
Parallax firewatch
by aldwin (@corvuscorax)
on CodePen.

Pure Parallax Scroll

See the Pen
parallax scroll
by gokulan (@gokvikash)
on CodePen.

Skewed Parallax Views

See the Pen
Skew-Clipped (SVG) Sections with Parallax Scrolling
by Nikolay Talanov (@suez)
on CodePen.

Parallax Header Magic

See the Pen
parallax scrolling Header and BG with ◇ indicator
by Alan Mok (@mok20123)
on CodePen.

CSS Parallax Universe

See the Pen
Parallax starry Universe (CSS only)
by Twan Mulder (@twanmulder)
on CodePen.

GSAP Parallax Thrill

See the Pen
Codepen Challenge – Scrolling GSAP Animation
by Shunya Koide (@shunyadezain)
on CodePen.

Parallax Clip Animation

See the Pen
Parallax clip
by Mikael Ainalem (@ainalem)
on CodePen.

Transparent Letter Drag-slider with Parallax

See the Pen
Responsive Parallax Drag-slider With Transparent Letters
by Ruslan Pivovarov (@mrspok407)
on CodePen.

Circle Parallax Animation

See the Pen
Raphael.js Parallax Circles .v2
by Chris Ryan (@chris-creditdesign)
on CodePen.

Parallax in Orbit

See the Pen
CSS Parallax Orbs
by Jamie Coulter (@jcoulterdesign)
on CodePen.

Text Blend Parallax Extravaganza

See the Pen
Parallax Effect + Text Blend Mode
by Fernando Cohen (@designfenix)
on CodePen.

Friendly Shadows with Parallax

See the Pen
Parallax Shadows (Mobile-Friendly)
by Janne Aukia (@jaukia)
on CodePen.

The Great Fall Experience

See the Pen
The Great Fall
by CJ Gammon (@cjgammon)
on CodePen.

Starry Parallax Backdrop

See the Pen
Parallax Star background in CSS
by sarazond (@sarazond)
on CodePen.

Mouse Moves, Parallax Grooves

See the Pen
Mouse Move Parallax ✨
by oscicen (@oscicen)
on CodePen.

Scroll Magic by Sebastian Schepis

See the Pen
CSS Scrolling Parallax Effect
by Sebastian Schepis (@sschepis)
on CodePen.

8-bit Grooviness

See the Pen
8-Bit CSS3 Horizontal Parallax
by Dan Stuart (@danbhala)
on CodePen.

Full Page Parallax Goodness

See the Pen
Full Page Parallax Scroll Effect
by Emily Hayman (@eehayman)
on CodePen.

Slick Horizontal Gallery

See the Pen
CSS-Only Horizontal Parallax Gallery
by Paulina Hetman (@pehaa)
on CodePen.

CSS Magic by Anish Shrestha

See the Pen
Apple TV parallax Pure CSS
by anish shrestha (@anish_code)
on CodePen.

Underneath It All by Paulo Cunha

See the Pen
CSS Parallax
by Paulo Cunha (@paulomrcunha)
on CodePen.

Top-Notch Parallax Header

See the Pen
Header Image Parallax Scrolling Effect with CSS
by WebMadeWell (@webmadewell)
on CodePen.

Bird’s View Parallax Party

See the Pen
Bird’s Eye View
by Sharna Hossain (@sharnajh)
on CodePen.

Parallax Hero Moment

See the Pen
CSS Parallax Hero
by Ryan Mulligan (@hexagoncircle)
on CodePen.

Katie’s Wedding Page Wonder

See the Pen
Parallax Design
by Katie Rogers (@kitkatkt)
on CodePen.

Parallax Sassiness by Scott Kellum

See the Pen
Sass parallax example
by Scott Kellum (@scottkellum)
on CodePen.

CSS Parallax FAQ

Can CSS parallax run horizontally?

See the Pen
Horizontal progression of vertical scroll
by MRU (@ruffiem)
on CodePen.

Yes. Scroll-driven animations accept an inline axis through scroll(inline) and view(inline), so a horizontal scroller can drive translateX the way vertical scrolling drives translateY.

Rellax supports horizontal parallax too, but its README keeps it disabled by default.

How many layers are too many?

See the Pen
Parallax Landscape CSS only
by Dave Chenell (@dchen05)
on CodePen.

No source sets a fixed limit. Each compositor layer takes memory (web.dev), and the cost shows first on low-memory phones.

Start with 2 or 3 layers per section, then check the Layers view in Chrome DevTools and scroll on a mid-range phone.

Does parallax affect SEO?

Parallax is a presentation effect, not a ranking signal. Its side effects matter more, since layout shift and heavy hero images feed Core Web Vitals.

Text that exists only inside a background image cannot be read as text, so keep headlines in HTML.

What image size and format suit parallax layers?

See the Pen
Image cutout, parallax effect: CSS + SVG
by Alex O’Neal (@alexoneal)
on CodePen.

Export each layer at the width it renders, not the width of its box. A layer scaled by 2 renders at twice its box, so a small source turns soft.

Compress the file as WebP or AVIF, and load the hero layer eagerly.

Does parallax work in WordPress page builders like Elementor?

Yes, with limits. Builders that rely on background-attachment: fixed inherit the iOS limitation, and Elementor’s help center says so.

Elementor recommends its Motion Effects instead, or setting the attachment back to Default on mobile.

What to Test Before Shipping CSS Parallax

Start with the reduced-motion fallback, then check a real phone. After that, look at the layer and layout-shift profile, and finish with text contrast at the worst frame.

The order follows who gets excluded. Without a fallback, visitors with vestibular disorders get hurt. A broken phone layout affects every mobile visitor, and too many layers mostly punish low-memory devices.

Contrast comes last because it is the only check that a scrim layer fixes in minutes.

Visitors on reduced-motion settings and on browsers without scroll-driven animations see a flat page, and that’s by design. The layout has to hold up without the motion.

As of October 2026, MDN does not mark scroll-driven animations as Baseline. The static fallback stays in the build until that label changes.

The same reduced-motion gating applies to every scroll-linked effect, including those in the guide to CSS animations on scroll.

Bogdan Sandu
Latest posts by Bogdan Sandu (see all)