Hero fade parallax
As you leave the hero, the background sinks slowly, the headline lifts away and fades, and the next section slides up over it.
CSSJavaScriptBeginner
<section class="hero" id="hero"><div class="bg" id="bg"></div>
<div class="copy" id="copy"><h1>Leave the hero</h1><p>Scroll down</p></div></section>
<section class="next"><h2>The page begins</h2><p>The next section slides up over the hero.</p></section>
body{display:block;height:auto}
.hero{position:relative;height:100vh;overflow:hidden;display:grid;place-items:center;text-align:center}
.bg{position:absolute;inset:-10% 0;background:radial-gradient(circle at 30% 30%,#8a3a22,#2a0f0b 55%,#140807);will-change:transform}
.copy{position:relative;will-change:transform,opacity}
.copy h1{margin:0;font-size:clamp(2.4rem,10vw,4.6rem);letter-spacing:-.04em}
.copy p{margin:12px 0 0;color:#cfc3c1}
.next{position:relative;z-index:1;min-height:100vh;padding:60px 24px;background:#140807;border-radius:28px 28px 0 0;margin-top:-28px;text-align:center;color:#cfc3c1}
.next h2{color:#fff}
const bg = document.getElementById('bg');
const copy = document.getElementById('copy');
function update() {
const y = scrollY, h = innerHeight;
if (y > h) return; // hero is gone, do nothing
const p = y / h; // 0 to 1
bg.style.transform = `translate3d(0, ${y * 0.45}px, 0) scale(${1 + p * 0.1})`; // sinks slowly
copy.style.transform = `translate3d(0, ${y * -0.25}px, 0)`; // lifts away
copy.style.opacity = 1 - p * 1.4;
}
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>Hero fade 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;height:auto}
.hero{position:relative;height:100vh;overflow:hidden;display:grid;place-items:center;text-align:center}
.bg{position:absolute;inset:-10% 0;background:radial-gradient(circle at 30% 30%,#8a3a22,#2a0f0b 55%,#140807);will-change:transform}
.copy{position:relative;will-change:transform,opacity}
.copy h1{margin:0;font-size:clamp(2.4rem,10vw,4.6rem);letter-spacing:-.04em}
.copy p{margin:12px 0 0;color:#cfc3c1}
.next{position:relative;z-index:1;min-height:100vh;padding:60px 24px;background:#140807;border-radius:28px 28px 0 0;margin-top:-28px;text-align:center;color:#cfc3c1}
.next h2{color:#fff}
</style></head>
<body>
<section class="hero" id="hero"><div class="bg" id="bg"></div>
<div class="copy" id="copy"><h1>Leave the hero</h1><p>Scroll down</p></div></section>
<section class="next"><h2>The page begins</h2><p>The next section slides up over the hero.</p></section>
<script>
const bg = document.getElementById('bg');
const copy = document.getElementById('copy');
function update() {
const y = scrollY, h = innerHeight;
if (y > h) return; // hero is gone, do nothing
const p = y / h; // 0 to 1
bg.style.transform = `translate3d(0, ${y * 0.45}px, 0) scale(${1 + p * 0.1})`; // sinks slowly
copy.style.transform = `translate3d(0, ${y * -0.25}px, 0)`; // lifts away
copy.style.opacity = 1 - p * 1.4;
}
addEventListener('scroll', () => requestAnimationFrame(update), { passive: true });
update();
</script>
</body></html>
Good for
The classic landing page opener. Simple and effective.
Watch out for
Stop updating once the hero is off screen, and give the headline will-change: transform, opacity only while it is moving.
Accessibility
Wrap motion in @media (prefers-reduced-motion: reduce) and switch it off or shorten it for people who ask for less movement.