Opposing rows

Rows of oversized words drift left and right in alternating directions as you scroll.

How it works

Each row is far wider than the screen. On scroll, odd rows translate by +scrollY × 0.4 and even rows by −scrollY × 0.4, starting from an offset so there is always text on both sides.

Full file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Opposing rows</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: #fdf041; color: #111; overflow-x: hidden; }
.pad { height: 60vh; }
.rows { display: grid; gap: 1vh; }
.row {
  white-space: nowrap; font: 900 clamp(3rem, 11vw, 9rem)/1 system-ui, sans-serif;
  text-transform: uppercase; letter-spacing: -.03em; will-change: transform;
}
.row.out { color: transparent; -webkit-text-stroke: 2px #111; }
</style>
</head>
<body>
<div class="pad"></div>
<div class="rows">
  <div class="row">scroll · box · scroll · box · scroll · box · scroll · box ·</div>
  <div class="row out">move · move · move · move · move · move · move · move ·</div>
  <div class="row">left · right · left · right · left · right · left · right ·</div>
  <div class="row out">depth · speed · depth · speed · depth · speed · depth ·</div>
  <div class="row">scroll · box · scroll · box · scroll · box · scroll · box ·</div>
  <div class="row out">again · again · again · again · again · again · again ·</div>
</div>
<div class="pad"></div>
<script>
const rows = document.querySelectorAll('.row');
function update() {
  rows.forEach((r, i) => {
    const dir = i % 2 ? 1 : -1;
    const x = -400 + dir * scrollY * 0.4 + (dir > 0 ? -600 : 0);
    r.style.transform = 'translate3d(' + x + 'px,0,0)';
  });
}
addEventListener('scroll', update, { passive: true });
update();
</script>
</body>
</html>

More parallax effects