Split columns

Three columns of tiles glide in opposite directions while the page holds still.

How it works

A pinned stage holds three tall columns. Scroll progress moves the outer columns up and the middle column down by the same amount, so the grid seems to shear past itself.

Full file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Split columns</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; }
body { font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; -webkit-font-smoothing: antialiased; }

body { background: #faf3dd; color: #2f3e46; }
.track { height: 300vh; }
.stage { position: sticky; top: 0; height: 100vh; overflow: hidden; }
.cols { display: grid; grid-template-columns: repeat(3, 1fr); gap: 2vw; padding: 0 3vw; }
.col { display: grid; gap: 2vw; will-change: transform; }
.col i { aspect-ratio: .8; border-radius: 14px; background: var(--c); }
</style>
</head>
<body>
<section class="track">
  <div class="stage"><div class="cols" id="cols"></div></div>
</section>
<script>
const colors = ['#e07a5f', '#3d405b', '#81b29a', '#f2cc8f', '#c1121f', '#669bbc', '#ffb4a2', '#6d6875'];
const cols = document.getElementById('cols');
const els = [0, 1, 2].map(k => {
  const c = document.createElement('div');
  c.className = 'col';
  for (let i = 0; i < 9; i++) c.insertAdjacentHTML('beforeend', '<i style="--c:' + colors[(i * 3 + k * 5) % colors.length] + '"></i>');
  cols.appendChild(c);
  return c;
});
const track = document.querySelector('.track');
function update() {
  const r = track.getBoundingClientRect();
  const p = Math.min(1, Math.max(0, -r.top / (r.height - innerHeight)));
  const range = els[0].offsetHeight - innerHeight;
  els.forEach((c, k) => {
    const y = k === 1 ? -range + p * range : -p * range;
    c.style.transform = 'translate3d(0,' + y + 'px,0)';
  });
}
addEventListener('scroll', update, { passive: true });
addEventListener('resize', update);
update();
</script>
</body>
</html>

More parallax effects