Intro loader with counter
A full-screen loader counts to 100, then splits open to reveal the page. Shown once per session.
CSSJavaScriptAdvanced
<main class="page"><h1>Welcome.</h1></main>
<div class="intro" aria-hidden="true">
<div class="half top"></div><div class="half bot"></div>
<div class="count" id="n">0</div>
</div>
<p class="replayhint">Use Replay above to run it again</p>
body{display:block;overflow:hidden}
.page{position:absolute;inset:0;display:grid;place-items:center;text-align:center;background:#140807}
.page h1{margin:0;font-size:clamp(2.4rem,10vw,4.6rem);letter-spacing:-.04em;opacity:0;transform:translateY(30px);transition:opacity .9s .5s,transform .9s .5s cubic-bezier(.2,.8,.2,1)}
.ready .page h1{opacity:1;transform:none}
.intro{position:fixed;inset:0;z-index:10;display:grid;place-items:center;pointer-events:none}
.half{position:absolute;left:0;right:0;height:50%;background:#f08a5d;transition:transform .9s cubic-bezier(.77,0,.18,1)}
.half.top{top:0}.half.bot{bottom:0}
.ready .half.top{transform:translateY(-100%)}.ready .half.bot{transform:translateY(100%)}
.count{position:relative;z-index:2;font:800 clamp(3rem,14vw,7rem)/1 system-ui;color:#270100;letter-spacing:-.05em;font-variant-numeric:tabular-nums;transition:opacity .3s,transform .5s}
.ready .count{opacity:0;transform:scale(1.3)}
.replayhint{position:fixed;bottom:14px;left:0;right:0;text-align:center;color:#a6938f;font-size:13px;z-index:20}
@media (prefers-reduced-motion:reduce){.intro{display:none}.page h1{opacity:1;transform:none}}
const n = document.getElementById('n');
const dur = 1800, t0 = performance.now();
(function tick(now) {
const p = Math.min((now - t0) / dur, 1);
n.textContent = Math.round(p * p * (3 - 2 * p) * 100); // smoothstep: slow, quick, slow
if (p < 1) requestAnimationFrame(tick);
else setTimeout(() => document.body.classList.add('ready'), 150);
})(t0);
<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Intro loader</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}
.page{position:absolute;inset:0;display:grid;place-items:center;text-align:center;background:#140807}
.page h1{margin:0;font-size:clamp(2.4rem,10vw,4.6rem);letter-spacing:-.04em;opacity:0;transform:translateY(30px);transition:opacity .9s .5s,transform .9s .5s cubic-bezier(.2,.8,.2,1)}
.ready .page h1{opacity:1;transform:none}
.intro{position:fixed;inset:0;z-index:10;display:grid;place-items:center;pointer-events:none}
.half{position:absolute;left:0;right:0;height:50%;background:#f08a5d;transition:transform .9s cubic-bezier(.77,0,.18,1)}
.half.top{top:0}.half.bot{bottom:0}
.ready .half.top{transform:translateY(-100%)}.ready .half.bot{transform:translateY(100%)}
.count{position:relative;z-index:2;font:800 clamp(3rem,14vw,7rem)/1 system-ui;color:#270100;letter-spacing:-.05em;font-variant-numeric:tabular-nums;transition:opacity .3s,transform .5s}
.ready .count{opacity:0;transform:scale(1.3)}
.replayhint{position:fixed;bottom:14px;left:0;right:0;text-align:center;color:#a6938f;font-size:13px;z-index:20}
@media (prefers-reduced-motion:reduce){.intro{display:none}.page h1{opacity:1;transform:none}}
</style></head>
<body>
<main class="page"><h1>Welcome.</h1></main>
<div class="intro" aria-hidden="true">
<div class="half top"></div><div class="half bot"></div>
<div class="count" id="n">0</div>
</div>
<p class="replayhint">Use Replay above to run it again</p>
<script>
const n = document.getElementById('n');
const dur = 1800, t0 = performance.now();
(function tick(now) {
const p = Math.min((now - t0) / dur, 1);
n.textContent = Math.round(p * p * (3 - 2 * p) * 100); // smoothstep: slow, quick, slow
if (p < 1) requestAnimationFrame(tick);
else setTimeout(() => document.body.classList.add('ready'), 150);
})(t0);
</script>
</body></html>
Good for
Brand-led sites that want a deliberate first impression. Keep it short, around 2 seconds.
Watch out for
Never block the page on it. Show once per session, let people skip, and honour reduced motion by removing it.
Accessibility
Wrap motion in @media (prefers-reduced-motion: reduce) and switch it off or shorten it for people who ask for less movement.