Scroll Parallax Layers — Free GSAP ScrollTrigger Snippet
Scroll Parallax Layers · Scroll · Plain HTML, CSS & JS · Live preview
Copy & paste
Source Code
<section class="pl-top"><p>Scroll ↓</p></section>
<section class="pl-scene" id="plScene">
<div class="pl-layer pl-sky" data-speed="0.15"></div>
<div class="pl-layer pl-hills" data-speed="0.4"></div>
<div class="pl-layer pl-mid" data-speed="0.7"></div>
<h2 class="pl-title" data-speed="0.9">PARALLAX</h2>
<div class="pl-layer pl-fore" data-speed="1.25"></div>
</section>
<section class="pl-bottom"><p>Layers moved at different speeds.</p></section>*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#0b0d18;color:#fff}
.pl-top,.pl-bottom{min-height:70vh;display:flex;justify-content:center;align-items:center;color:#8a90a8;font-size:15px;letter-spacing:.1em;text-transform:uppercase}
.pl-scene{position:relative;height:120vh;overflow:hidden;background:linear-gradient(180deg,#1a2350,#0b0d18)}
.pl-layer{position:absolute;left:0;right:0;will-change:transform}
.pl-sky{top:0;height:80%;background:radial-gradient(50% 60% at 70% 20%,rgba(255,235,180,.5),transparent 60%),radial-gradient(40% 50% at 30% 30%,rgba(140,170,255,.35),transparent 60%)}
.pl-hills{bottom:0;height:55%;background:radial-gradient(120% 100% at 30% 100%,#2b3f8a 0 60%,transparent 61%),radial-gradient(120% 100% at 80% 100%,#27457e 0 55%,transparent 56%)}
.pl-mid{bottom:0;height:38%;background:radial-gradient(140% 100% at 60% 100%,#1d2c63 0 60%,transparent 61%)}
.pl-fore{bottom:-4%;height:26%;background:radial-gradient(160% 100% at 40% 100%,#10183a 0 70%,transparent 71%)}
.pl-title{position:absolute;top:42%;left:0;right:0;text-align:center;font-size:clamp(40px,12vw,150px);font-weight:900;letter-spacing:.04em;color:#fff;text-shadow:0 10px 40px rgba(0,0,0,.4);will-change:transform}gsap.registerPlugin(ScrollTrigger);
// Move each layer by a multiple of the scroll distance: smaller = slower = "farther".
gsap.utils.toArray('[data-speed]').forEach(function (layer) {
var speed = parseFloat(layer.dataset.speed);
gsap.to(layer, {
yPercent: -40 * speed, // farther layers travel less, near layers more
ease: 'none',
scrollTrigger: {
trigger: '#plScene',
start: 'top bottom',
end: 'bottom top',
scrub: true
}
});
});Extend with AI
Take This Snippet Further With AI
The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.
What's included
Features
About this UI Snippet
Scroll Parallax Layers — Depth From Speed-Differentiated Layers

Scroll parallax is the depth illusion where background layers drift slowly and foreground layers move quickly as you scroll, so the scene feels three-dimensional. This snippet builds a multi-layer parallax scene with GSAP and ScrollTrigger (from a CDN), driven entirely by a single data-speed attribute per layer.
Speed as data
Every parallax layer declares how fast it should move with a data-speed value — small numbers for distant layers (sky), larger ones for near layers (foreground). The script reads each value and creates a tween that translates the layer by yPercent: -40 × speed as the scene scrolls. Encoding speed in markup means adding or retuning a layer is a one-attribute change, and one loop wires them all up; there are no per-layer constants buried in the JavaScript.
Scrubbed across the scene
Each layer's tween is tied to a ScrollTrigger on the scene with start: 'top bottom' and end: 'bottom top', so the parallax plays across the entire time the scene is in view — from when its top enters the bottom of the viewport to when its bottom exits the top. scrub: true binds the motion to the scrollbar so the layers separate and re-converge smoothly as you scroll either direction. ease: 'none' keeps the speed differences constant and predictable.
Why differential speed reads as depth
Our eyes infer distance from motion parallax: things farther away appear to move less. By moving the sky a little and the foreground a lot for the same scroll, the snippet reproduces that cue, and the brain reads the flat layers as receding into space. The headline sits at a middle speed so it feels embedded in the scene rather than pasted on top.
Transform-only and smooth
All movement is yPercent (a GSAP transform), so the browser composites the layers on the GPU without reflowing or repainting layout — essential when several layers animate at once on every scroll frame. will-change: transform and overflow: hidden on the scene keep the moving layers contained and jank-free.
Asset-free scene
The layers are built from CSS radial gradients (sky glow, rolling hills, foreground ridge) so the snippet needs no images. In production you'd swap each layer's background for a transparent PNG or SVG; the parallax logic is unchanged because it only animates transforms.
Customizing it
Add more layers with their own data-speed, change the base travel multiplier, swap the gradients for real art, or add horizontal parallax with xPercent. Pair it with a parallax hero, a scroll zoom hero, or a hero parallax grid.
Step by step
How to Use
- 1Add the GSAP CDNsInclude gsap and ScrollTrigger from the CDN panel.
- 2Paste HTML, CSS, and JSA layered scene renders between two spacers.
- 3Scroll through the sceneLayers move at different speeds for depth.
- 4Scroll back upThe layers re-converge — motion is scrubbed.
- 5Retune a layerChange its data-speed value.
- 6Use real artSwap each layer's gradient for a PNG/SVG.
Real-world uses
Common Use Cases
data-speed attribute per layer.yPercent transforms that composite on the GPU for depth.forEach.Got questions?
Frequently Asked Questions
Every layer carries a data-speed value, and the script reads it and tweens the layer by yPercent: -40 times that speed. Small values move distant layers a little; large values move near layers a lot. Encoding speed in the attribute means one loop wires every layer and retuning is a single-attribute change.
Each tween is tied to a ScrollTrigger on the scene with start: top bottom and end: bottom top, so it runs the whole time the scene is in view — from its top entering the viewport bottom to its bottom leaving the top. scrub: true binds it to the scrollbar so layers separate and re-converge as you scroll either way.
Motion parallax is a real depth cue: distant objects appear to move less than near ones. Moving the sky slightly and the foreground a lot for the same scroll reproduces that cue, so the brain reads the flat layers as receding into space. The headline uses a middle speed so it feels embedded in the scene.
Yes, because all movement is yPercent, a transform, so the browser composites the layers on the GPU without reflowing or repainting layout. will-change: transform promotes them and overflow: hidden on the scene contains them, so even multiple layers moving every frame stay smooth.
Render the layers with their data-speed attributes, then in a mount effect register ScrollTrigger and loop the layers (via a scoped ref query) to create the tweens. Return a cleanup that reverts the GSAP context so the triggers are removed on unmount. Swap gradients for image layers; the transform-based logic and CSS port unchanged.