Reusable data-speed parallax
A tiny drop-in script: put data-speed on any element and it moves at that speed relative to the scroll. Negative values go the other way.
CSSJavaScriptBeginner
<div class="space">Scroll down</div>
<div class="field">
<div class="el a" data-speed="0.3">speed<br>0.3</div>
<div class="el b" data-speed="-0.2">speed<br>-0.2</div>
<div class="el c" data-speed="0.1">speed<br>0.1</div>
<div class="el d" data-speed="-0.35">speed<br>-0.35</div>
<div class="el e" data-speed="0.5">speed<br>0.5</div>
</div>
<div class="space">The end</div>
body{display:block;height:auto;overflow-x:hidden}
.space{height:40vh;display:grid;place-items:center;color:#a6938f;font-size:14px}
.field{position:relative;height:150vh;width:min(480px,92vw);margin:0 auto}
.el{position:absolute;display:grid;place-items:center;font-weight:800;color:#270100;border-radius:14px;font-size:14px;text-align:center;line-height:1.2}
.a{left:0;top:6%;width:120px;height:120px;background:#f08a5d}
.b{right:4%;top:20%;width:90px;height:90px;border-radius:50%;background:#f3d9c8}
.c{left:26%;top:44%;width:170px;height:100px;background:#e8b04c}
.d{right:0;top:66%;width:110px;height:110px;background:#d98a8a}
.e{left:6%;top:82%;width:80px;height:80px;border-radius:50%;background:#b07a6a}
// Parallax utility: <div data-speed="0.3"> (positive = slower than scroll, negative = faster)
const items = [...document.querySelectorAll('[data-speed]')];
function update() {
const vh = innerHeight;
items.forEach(el => {
const r = el.parentElement.getBoundingClientRect();
if (r.bottom < -200 || r.top > vh + 200) return; // far off screen: skip
const fromCenter = r.top + r.height / 2 - vh / 2; // 0 when the parent is centred
el.style.transform = `translate3d(0, ${fromCenter * -el.dataset.speed}px, 0)`;
});
}
addEventListener('scroll', () => requestAnimationFrame(update), { passive: true });
addEventListener('resize', update);
update();
<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Reusable data-speed parallax</title>
<style>
*{box-sizing:border-box}
html,body{margin:0;height:100%}
body{display:grid;place-items:center;background:#140807;color:#fffffc;font:16px/1.5 system-ui,-apple-system,"Segoe UI",sans-serif}
body{display:block;place-items:normal;height:auto;overflow-x:hidden}
.space{height:40vh;display:grid;place-items:center;color:#a6938f;font-size:14px}
.field{position:relative;height:150vh;width:min(480px,92vw);margin:0 auto}
.el{position:absolute;display:grid;place-items:center;font-weight:800;color:#270100;border-radius:14px;font-size:14px;text-align:center;line-height:1.2}
.a{left:0;top:6%;width:120px;height:120px;background:#f08a5d}
.b{right:4%;top:20%;width:90px;height:90px;border-radius:50%;background:#f3d9c8}
.c{left:26%;top:44%;width:170px;height:100px;background:#e8b04c}
.d{right:0;top:66%;width:110px;height:110px;background:#d98a8a}
.e{left:6%;top:82%;width:80px;height:80px;border-radius:50%;background:#b07a6a}
</style></head>
<body>
<div class="space">Scroll down</div>
<div class="field">
<div class="el a" data-speed="0.3">speed<br>0.3</div>
<div class="el b" data-speed="-0.2">speed<br>-0.2</div>
<div class="el c" data-speed="0.1">speed<br>0.1</div>
<div class="el d" data-speed="-0.35">speed<br>-0.35</div>
<div class="el e" data-speed="0.5">speed<br>0.5</div>
</div>
<div class="space">The end</div>
<script>
// Parallax utility: <div data-speed="0.3"> (positive = slower than scroll, negative = faster)
const items = [...document.querySelectorAll('[data-speed]')];
function update() {
const vh = innerHeight;
items.forEach(el => {
const r = el.parentElement.getBoundingClientRect();
if (r.bottom < -200 || r.top > vh + 200) return; // far off screen: skip
const fromCenter = r.top + r.height / 2 - vh / 2; // 0 when the parent is centred
el.style.transform = `translate3d(0, ${fromCenter * -el.dataset.speed}px, 0)`;
});
}
addEventListener('scroll', () => requestAnimationFrame(update), { passive: true });
addEventListener('resize', update);
update();
</script>
</body></html>
Good for
Your default starting point. Copy this script into any project and sprinkle data-speed wherever you want depth.
Watch out for
Measure the parent, not the element, because the element is being transformed. Skip anything far off screen to stay fast.
Accessibility
Wrap motion in @media (prefers-reduced-motion: reduce) and switch it off or shorten it for people who ask for less movement.