Parallax Layers
The layers of a scene drift at different speeds as you scroll past it: the sun and far hills lag behind while the near ones rush by, giving a flat page real depth. Each layer gets its own speed.
Scroll inside ↓
Parallax
How to use it
- Paste the snippet just before
</head>. - Add
data-parallax-layersto the scene’s container. - Give each layer inside it a
data-depth:0scrolls with the page,0.5at half speed,1stays put, and negative values move faster than the page.
Options
Add any of these attributes to the same element.
data-depth | On each layer: how much it lags behind the scroll (see above). |
data-smooth | On the container: ease the layers towards their place instead of following the scroll exactly. |
Give background layers some extra height, or overflow: hidden on the container, so their edges never come into view as they drift. Works inside scrolling boxes too.
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
(function () {
// Parallax Layers: the layers of a scene drift at different speeds as you
// scroll past it, so distant things lag behind and near things rush by,
// giving a flat page depth.
// Usage: <div data-parallax-layers>
// <img data-depth="0.6" src="sky.png">
// <img data-depth="0.3" src="hills.png">
// <h2 data-depth="-0.2">Foreground</h2>
// </div>
// data-depth on each layer sets its speed: 0 scrolls with the page, 0.5 at
// half speed, 1 stays put on screen, and negative values move faster than
// the page. Layers without data-depth scroll normally.
// Optional: data-smooth (on the container: ease the layers towards their
// place instead of following the scroll exactly).
// Give background layers some extra height (or overflow:hidden on the
// container) so their edges never come into view as they drift.
// Works inside scrolling boxes too: it follows the nearest scrolling parent.
function scroller(el) {
for (var p = el.parentElement; p && p !== document.body; p = p.parentElement) {
var o = getComputedStyle(p).overflowY;
if ((o === 'auto' || o === 'scroll') && p.scrollHeight > p.clientHeight) return p;
}
return null;
}
function setup(el) {
var layers = Array.prototype.slice.call(el.querySelectorAll('[data-depth]')).map(function (n) {
n.style.willChange = 'transform';
return { el: n, depth: parseFloat(n.dataset.depth) || 0, y: 0 };
});
if (!layers.length) return;
var box = scroller(el);
var smooth = el.dataset.smooth !== undefined && el.dataset.smooth !== 'false';
var running = false;
// How far the scene's centre is from the centre of the view, in px. A
// layer moved back by depth × that distance travels at (1 - depth) speed.
function offset() {
var r = el.getBoundingClientRect();
var top = 0, h = window.innerHeight;
if (box) { var b = box.getBoundingClientRect(); top = b.top + box.clientTop; h = box.clientHeight; }
if (r.bottom < top - h || r.top > top + 2 * h) return null; // well out of view
return r.top + r.height / 2 - (top + h / 2);
}
function frame() {
running = false;
var dist = offset();
if (dist === null) return;
var moving = false;
layers.forEach(function (l) {
var target = -l.depth * dist;
l.y = smooth ? l.y + (target - l.y) * 0.12 : target;
if (Math.abs(target - l.y) > 0.3) moving = true;
else l.y = target;
l.el.style.transform = 'translate3d(0,' + l.y.toFixed(2) + 'px,0)';
});
if (moving) request();
}
function request() {
if (running) return;
running = true;
requestAnimationFrame(frame);
}
(box || window).addEventListener('scroll', request, { passive: true });
window.addEventListener('resize', request);
request();
}
function init() {
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
document.querySelectorAll('[data-parallax-layers]').forEach(function (el) {
if (el._plx) return;
el._plx = true;
setup(el);
});
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();
</script><div class="hero" data-parallax-layers style="overflow: hidden">
<img src="sky.png" data-depth="0.7" alt="">
<img src="hills.png" data-depth="0.4" alt="">
<h1 data-depth="0.2">Into the hills</h1>
</div>