Pinned hero that shrinks
A full-screen hero stays pinned and scales into a rounded card as you scroll, revealing the page beneath.
CSSJavaScriptIntermediate
<div class="wrap" id="wrap"><div class="pin"><div class="hero" id="hero"><h1>Scroll to<br>shrink me</h1></div></div></div>
<div class="after"><p>The page continues beneath.</p></div>
body{display:block;height:auto}
.wrap{height:220vh}
.pin{position:sticky;top:0;height:100vh;display:grid;place-items:center;overflow:hidden}
.hero{width:100%;height:100%;background:radial-gradient(circle at 30% 30%,#8a3a22,#2a0f0b 60%,#140807);
display:grid;place-items:center;will-change:transform}
.hero h1{margin:0;font-size:clamp(2.2rem,9vw,4rem);letter-spacing:-.03em;text-align:center}
.after{min-height:60vh;display:grid;place-items:center;color:#cfc3c1;text-align:center;padding:40px}
const wrap = document.getElementById('wrap');
const hero = document.getElementById('hero');
function update() {
const max = wrap.offsetHeight - innerHeight;
const p = Math.min(Math.max(scrollY / max, 0), 1); // 0 to 1
const scale = 1 - p * 0.35;
hero.style.transform = `scale(${scale})`;
hero.style.borderRadius = `${p * 40}px`;
hero.style.opacity = 1 - p * 0.35;
}
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>Pinned shrinking hero</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}
.wrap{height:220vh}
.pin{position:sticky;top:0;height:100vh;display:grid;place-items:center;overflow:hidden}
.hero{width:100%;height:100%;background:radial-gradient(circle at 30% 30%,#8a3a22,#2a0f0b 60%,#140807);
display:grid;place-items:center;will-change:transform}
.hero h1{margin:0;font-size:clamp(2.2rem,9vw,4rem);letter-spacing:-.03em;text-align:center}
.after{min-height:60vh;display:grid;place-items:center;color:#cfc3c1;text-align:center;padding:40px}
</style></head>
<body>
<div class="wrap" id="wrap"><div class="pin"><div class="hero" id="hero"><h1>Scroll to<br>shrink me</h1></div></div></div>
<div class="after"><p>The page continues beneath.</p></div>
<script>
const wrap = document.getElementById('wrap');
const hero = document.getElementById('hero');
function update() {
const max = wrap.offsetHeight - innerHeight;
const p = Math.min(Math.max(scrollY / max, 0), 1); // 0 to 1
const scale = 1 - p * 0.35;
hero.style.transform = `scale(${scale})`;
hero.style.borderRadius = `${p * 40}px`;
hero.style.opacity = 1 - p * 0.35;
}
addEventListener('scroll', () => requestAnimationFrame(update), { passive: true });
update();
</script>
</body></html>
Good for
Opening a landing page with a cinematic hero that hands over to content.
Watch out for
Drive transform and border-radius from one progress value, and clamp it between 0 and 1.
Accessibility
Wrap motion in @media (prefers-reduced-motion: reduce) and switch it off or shorten it for people who ask for less movement.