Giant text parallax
Huge faded words sit behind the content and drift at a different speed from the cards on top of them.
CSSJavaScriptBeginner
<section class="section" id="sec">
<div class="giant" id="giant" aria-hidden="true">DEPTH</div>
<div class="cards">
<div class="card"><h2>Foreground</h2><p>I scroll at normal speed.</p></div>
<div class="card"><h2>Background</h2><p>The big word moves at a different pace.</p></div>
<div class="card"><h2>Together</h2><p>The gap between the speeds is the depth.</p></div>
</div>
</section>
body{display:block;height:auto;overflow-x:hidden}
.section{position:relative;min-height:130vh;padding:30vh 24px 20vh;overflow:hidden}
.giant{position:absolute;left:50%;top:30%;transform:translateX(-50%);white-space:nowrap;font-weight:900;letter-spacing:-.06em;line-height:.8;
font-size:clamp(7rem,40vw,16rem);color:transparent;-webkit-text-stroke:2px rgba(240,138,93,.35);pointer-events:none;will-change:transform}
.cards{position:relative;display:grid;gap:70px;width:min(340px,86vw);margin:0 auto}
.card{padding:26px;border-radius:14px;background:rgba(42,21,19,.92);border:1px solid #4a2e2a;backdrop-filter:blur(6px)}
.card h2{margin:0 0 6px}.card p{margin:0;color:#cfc3c1}
const giant = document.getElementById('giant');
const sec = document.getElementById('sec');
function update() {
const r = sec.getBoundingClientRect();
const fromCenter = r.top + r.height / 2 - innerHeight / 2;
giant.style.transform = `translate3d(-50%, ${fromCenter * -0.45}px, 0)`; // moves against the page
}
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>Giant text 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;overflow-x:hidden}
.section{position:relative;min-height:130vh;padding:30vh 24px 20vh;overflow:hidden}
.giant{position:absolute;left:50%;top:30%;transform:translateX(-50%);white-space:nowrap;font-weight:900;letter-spacing:-.06em;line-height:.8;
font-size:clamp(7rem,40vw,16rem);color:transparent;-webkit-text-stroke:2px rgba(240,138,93,.35);pointer-events:none;will-change:transform}
.cards{position:relative;display:grid;gap:70px;width:min(340px,86vw);margin:0 auto}
.card{padding:26px;border-radius:14px;background:rgba(42,21,19,.92);border:1px solid #4a2e2a;backdrop-filter:blur(6px)}
.card h2{margin:0 0 6px}.card p{margin:0;color:#cfc3c1}
</style></head>
<body>
<section class="section" id="sec">
<div class="giant" id="giant" aria-hidden="true">DEPTH</div>
<div class="cards">
<div class="card"><h2>Foreground</h2><p>I scroll at normal speed.</p></div>
<div class="card"><h2>Background</h2><p>The big word moves at a different pace.</p></div>
<div class="card"><h2>Together</h2><p>The gap between the speeds is the depth.</p></div>
</div>
</section>
<script>
const giant = document.getElementById('giant');
const sec = document.getElementById('sec');
function update() {
const r = sec.getBoundingClientRect();
const fromCenter = r.top + r.height / 2 - innerHeight / 2;
giant.style.transform = `translate3d(-50%, ${fromCenter * -0.45}px, 0)`; // moves against the page
}
addEventListener('scroll', () => requestAnimationFrame(update), { passive: true });
update();
</script>
</body></html>
Good for
Section backdrops, big wordmarks and editorial layouts.
Watch out for
Keep the giant text very low contrast so it never competes with real content, and mark it aria-hidden.
Accessibility
Wrap motion in @media (prefers-reduced-motion: reduce) and switch it off or shorten it for people who ask for less movement.