Multi-speed columns
A gallery whose columns scroll at different speeds, so the grid stretches and staggers as you move down.
CSSJavaScriptIntermediate
<div class="space">Scroll down</div>
<div class="grid" id="grid">
<div class="col" data-speed="0.12"><div class="tile t1">1</div><div class="tile t2">2</div><div class="tile t3">3</div><div class="tile t4">4</div></div>
<div class="col" data-speed="-0.1"><div class="tile t5">5</div><div class="tile t6">6</div><div class="tile t2">7</div><div class="tile t1">8</div></div>
<div class="col" data-speed="0.24"><div class="tile t3">9</div><div class="tile t4">10</div><div class="tile t5">11</div><div class="tile t6">12</div></div>
</div>
<div class="space">The end</div>
body{display:block;height:auto}
.space{height:55vh;display:grid;place-items:center;color:#a6938f;font-size:14px}
.grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;width:min(520px,92vw);margin:0 auto;padding-bottom:140px}
.col{display:grid;gap:12px;align-content:start;will-change:transform}
.tile{border-radius:12px;display:grid;place-items:center;font-weight:800;color:#270100}
.t1{height:150px;background:#f08a5d}.t2{height:210px;background:#f3d9c8}.t3{height:130px;background:#e8b04c}
.t4{height:190px;background:#d98a8a}.t5{height:160px;background:#b07a6a}.t6{height:120px;background:#a8523a}
const grid = document.getElementById('grid');
const cols = [...grid.querySelectorAll('[data-speed]')];
function update() {
const r = grid.getBoundingClientRect(); // one shared reference
const fromCenter = r.top + r.height / 2 - innerHeight / 2;
cols.forEach(c => {
c.style.transform = `translate3d(0, ${fromCenter * -c.dataset.speed}px, 0)`;
});
}
addEventListener('scroll', () => requestAnimationFrame(update), { passive: true });
update();
<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Multi-speed columns</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}
.space{height:55vh;display:grid;place-items:center;color:#a6938f;font-size:14px}
.grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;width:min(520px,92vw);margin:0 auto;padding-bottom:140px}
.col{display:grid;gap:12px;align-content:start;will-change:transform}
.tile{border-radius:12px;display:grid;place-items:center;font-weight:800;color:#270100}
.t1{height:150px;background:#f08a5d}.t2{height:210px;background:#f3d9c8}.t3{height:130px;background:#e8b04c}
.t4{height:190px;background:#d98a8a}.t5{height:160px;background:#b07a6a}.t6{height:120px;background:#a8523a}
</style></head>
<body>
<div class="space">Scroll down</div>
<div class="grid" id="grid">
<div class="col" data-speed="0.12"><div class="tile t1">1</div><div class="tile t2">2</div><div class="tile t3">3</div><div class="tile t4">4</div></div>
<div class="col" data-speed="-0.1"><div class="tile t5">5</div><div class="tile t6">6</div><div class="tile t2">7</div><div class="tile t1">8</div></div>
<div class="col" data-speed="0.24"><div class="tile t3">9</div><div class="tile t4">10</div><div class="tile t5">11</div><div class="tile t6">12</div></div>
</div>
<div class="space">The end</div>
<script>
const grid = document.getElementById('grid');
const cols = [...grid.querySelectorAll('[data-speed]')];
function update() {
const r = grid.getBoundingClientRect(); // one shared reference
const fromCenter = r.top + r.height / 2 - innerHeight / 2;
cols.forEach(c => {
c.style.transform = `translate3d(0, ${fromCenter * -c.dataset.speed}px, 0)`;
});
}
addEventListener('scroll', () => requestAnimationFrame(update), { passive: true });
update();
</script>
</body></html>
Good for
Image galleries, portfolios and team grids.
Watch out for
Calculate from the shared parent so every column uses the same reference. Add bottom padding so slower columns do not overlap the next section.
Accessibility
Wrap motion in @media (prefers-reduced-motion: reduce) and switch it off or shorten it for people who ask for less movement.