Layered landscape

Five mountain ridges slide at different speeds to build depth out of flat shapes.

How it works

The scene sits in a sticky container inside a tall section. Each layer has a data-speed; on scroll, the script moves it by scrollY × speed. Distant layers move least, near layers most — the classic parallax depth cue.

Full file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Layered landscape</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: #2a1340; color: #fff; }
.scene { height: 200vh; }
.sticky { position: sticky; top: 0; height: 100vh; overflow: hidden; }
.layer { position: absolute; left: 0; right: 0; bottom: 0; width: 100%; height: 60%; will-change: transform; }
.sky { top: 0; height: 100%; background: linear-gradient(#ffd3a5, #fd9f8e 45%, #c06c84); }
.sun { left: 50%; bottom: 35%; width: 22vmin; height: 22vmin; margin-left: -11vmin; border-radius: 50%; background: #fff3c4; right: auto; }
.title { position: absolute; top: 18%; width: 100%; text-align: center; margin: 0;
  font: 800 clamp(3rem, 12vw, 8rem)/1 system-ui, sans-serif; letter-spacing: -.04em; color: #4a2463; }
.after { min-height: 80vh; background: #4a2463; display: grid; place-content: center; padding: 0 10vw; }
.after p { font-size: 1.6rem; max-width: 30ch; text-align: center; }
</style>
</head>
<body>
<section class="scene">
  <div class="sticky">
    <div class="layer sky" data-speed="0"></div>
    <div class="layer sun" data-speed="0.15"></div>
    <svg class="layer" data-speed="0.25" viewBox="0 0 1200 400" preserveAspectRatio="none"><path fill="#f7a072" d="M0 260 L150 170 L300 230 L460 120 L620 220 L800 140 L980 230 L1200 150 V400 H0Z"/></svg>
    <svg class="layer" data-speed="0.4" viewBox="0 0 1200 400" preserveAspectRatio="none"><path fill="#e0617a" d="M0 300 L120 220 L280 290 L420 200 L600 300 L760 210 L940 290 L1080 230 L1200 270 V400 H0Z"/></svg>
    <svg class="layer" data-speed="0.6" viewBox="0 0 1200 400" preserveAspectRatio="none"><path fill="#9c3d7a" d="M0 330 L200 260 L380 330 L560 250 L760 340 L920 270 L1200 330 V400 H0Z"/></svg>
    <svg class="layer" data-speed="0.85" viewBox="0 0 1200 400" preserveAspectRatio="none"><path fill="#4a2463" d="M0 360 L240 300 L480 370 L700 310 L900 370 L1200 320 V400 H0Z"/></svg>
    <h1 class="title" data-speed="-0.3">Horizon</h1>
  </div>
</section>
<section class="after"><p>Depth from flat shapes: the farther a layer, the slower it moves.</p></section>
<script>
const layers = document.querySelectorAll('[data-speed]');
function update() {
  const y = scrollY;
  layers.forEach(l => {
    const s = parseFloat(l.dataset.speed);
    l.style.transform = 'translate3d(0,' + (y * s) + 'px,0)';
  });
}
addEventListener('scroll', update, { passive: true });
update();
</script>
</body>
</html>

More parallax effects