Hero zoom-out

A full-bleed hero shrinks into a framed card while its title drifts up and fades.

How it works

The hero is pinned for 200vh. Progress drives three things at once: the image’s scale and inset (so it pulls away from the edges), its corner radius, and the title’s translateY and opacity. Content below rises over it when the pin ends.

Full file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Hero zoom-out</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: #f4f1ea; color: #1d3557; }
.track { height: 220vh; }
.stage { position: sticky; top: 0; height: 100vh; overflow: hidden; display: grid; place-items: center; }
.hero { position: absolute; inset: 0; will-change: transform;
  background: radial-gradient(ellipse at 50% 120%, #1d3557 0 30%, transparent 31%), radial-gradient(circle at 70% 25%, #fefae0 0 6%, transparent 6.5%), linear-gradient(#a8dadc, #f1faee 60%, #e9c46a); }
h1 { position: relative; font: 900 clamp(3rem, 13vw, 9rem)/1 system-ui, sans-serif; letter-spacing: -.05em; margin: 0; color: #1d3557; }
.content { max-width: 680px; margin: 0 auto; padding: 10vh 24px 40vh; }
.content h2 { font: 800 2.4rem system-ui, sans-serif; }
.content p { font-size: 1.2rem; line-height: 1.6; }
</style>
</head>
<body>
<section class="track">
  <div class="stage">
    <div class="hero" id="hero"></div>
    <h1 id="title">Wide open</h1>
  </div>
</section>
<main class="content"><h2>Then the page begins.</h2><p>The hero has stepped back into a frame, and the story can start.</p></main>
<script>
const hero = document.getElementById('hero'), title = document.getElementById('title');
const track = document.querySelector('.track');
function update() {
  const r = track.getBoundingClientRect();
  const p = Math.min(1, Math.max(0, -r.top / (r.height - innerHeight)));
  const inset = p * 8;
  hero.style.inset = inset + 'vh ' + inset + 'vw';
  hero.style.borderRadius = (p * 32) + 'px';
  hero.style.transform = 'scale(' + (1.25 - p * 0.25) + ')';
  title.style.transform = 'translateY(' + (-p * 30) + 'vh)';
  title.style.opacity = (1 - p * 1.4).toFixed(2);
}
addEventListener('scroll', update, { passive: true });
update();
</script>
</body>
</html>

More parallax effects