Pure CSS 3D parallax
Real depth with no JavaScript: a scroll container with perspective, and layers pushed back with translateZ then scaled up to look normal.
CSSAdvanced
<div class="scroller">
<section class="group">
<div class="layer back sky"><div class="sun"></div></div>
<div class="layer mid"><div class="hills"></div></div>
<div class="layer base"><h1>Pure CSS depth</h1></div>
</section>
<section class="plain"><h2>Back to normal</h2><p>No JavaScript touched this page.</p></section>
</div>
body{display:block;overflow:hidden}
.scroller{height:100vh;overflow-x:hidden;overflow-y:auto;perspective:1px;perspective-origin:0 0}
.group{position:relative;height:100vh;transform-style:preserve-3d}
.layer{position:absolute;inset:0;transform-origin:0 0} /* matches perspective-origin so scaling stays aligned */
.back{transform:translateZ(-2px) scale(3)} /* far: slowest. scale = 1 + 2/1 */
.mid{transform:translateZ(-1px) scale(2)} /* middle: scale = 1 + 1/1 */
.base{transform:translateZ(0);display:grid;place-items:center}
.sky{background:linear-gradient(#2a0f0b,#e5603a 90%)}
.sun{position:absolute;left:50%;top:44%;width:80px;height:80px;margin-left:-40px;border-radius:50%;background:#ffd166;box-shadow:0 0 50px #ffd166}
.hills{position:absolute;left:0;right:0;bottom:-5%;height:45%;background:#4e1408;clip-path:polygon(0 55%,18% 25%,38% 50%,60% 15%,82% 45%,100% 22%,100% 100%,0 100%)}
.base h1{margin:0;font-size:clamp(2.2rem,9vw,3.6rem);letter-spacing:-.03em;text-shadow:0 4px 30px rgba(0,0,0,.4)}
.plain{position:relative;min-height:100vh;padding:50px 24px;background:#140807;text-align:center;color:#cfc3c1}
.plain h2{color:#fff}
<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Pure CSS 3D 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;overflow:hidden}
.scroller{height:100vh;overflow-x:hidden;overflow-y:auto;perspective:1px;perspective-origin:0 0}
.group{position:relative;height:100vh;transform-style:preserve-3d}
.layer{position:absolute;inset:0;transform-origin:0 0} /* matches perspective-origin so scaling stays aligned */
.back{transform:translateZ(-2px) scale(3)} /* far: slowest. scale = 1 + 2/1 */
.mid{transform:translateZ(-1px) scale(2)} /* middle: scale = 1 + 1/1 */
.base{transform:translateZ(0);display:grid;place-items:center}
.sky{background:linear-gradient(#2a0f0b,#e5603a 90%)}
.sun{position:absolute;left:50%;top:44%;width:80px;height:80px;margin-left:-40px;border-radius:50%;background:#ffd166;box-shadow:0 0 50px #ffd166}
.hills{position:absolute;left:0;right:0;bottom:-5%;height:45%;background:#4e1408;clip-path:polygon(0 55%,18% 25%,38% 50%,60% 15%,82% 45%,100% 22%,100% 100%,0 100%)}
.base h1{margin:0;font-size:clamp(2.2rem,9vw,3.6rem);letter-spacing:-.03em;text-shadow:0 4px 30px rgba(0,0,0,.4)}
.plain{position:relative;min-height:100vh;padding:50px 24px;background:#140807;text-align:center;color:#cfc3c1}
.plain h2{color:#fff}
</style></head>
<body>
<div class="scroller">
<section class="group">
<div class="layer back sky"><div class="sun"></div></div>
<div class="layer mid"><div class="hills"></div></div>
<div class="layer base"><h1>Pure CSS depth</h1></div>
</section>
<section class="plain"><h2>Back to normal</h2><p>No JavaScript touched this page.</p></section>
</div>
</body></html>
Good for
Hero scenes and illustrated sections where you want true depth maths handled by the browser.
Watch out for
The scale to cancel the shrink is 1 + (distance / perspective). Test in Safari, which treats perspective on scroll containers slightly differently. Layers must overflow their group a little to avoid gaps.
Accessibility
Wrap motion in @media (prefers-reduced-motion: reduce) and switch it off or shorten it for people who ask for less movement.