Scroll reveal

스크롤 리빌

Triggering a fade-in or slide-up animation the moment an element enters the viewport, using IntersectionObserver.

Also known as: Reveal on scrollFade-up on scroll
···
html
<div class="viewport" id="vp">
  <div class="track" id="track">
    <div class="row">Row A</div>
    <div class="row">Row B</div>
    <div class="row">Row C</div>
    <div class="row">Row D</div>
    <div class="row">Row A</div>
    <div class="row">Row B</div>
    <div class="row">Row C</div>
    <div class="row">Row D</div>
  </div>
  <div class="line"></div>
</div>
css
.viewport{position:absolute;inset:6%;overflow:hidden;border:1px solid var(--line);border-radius:12px;background:var(--surface)}
.track{display:flex;flex-direction:column;gap:14px;padding:16px}
.row{padding:12px 16px;border-radius:10px;background:var(--bg);border:1px solid var(--line);color:var(--muted);
  font:600 13px/1.3 sans-serif;opacity:0;transform:translateY(24px);transition:opacity .5s ease-out,transform .5s ease-out,border-color .5s,color .5s}
.row.in{opacity:1;transform:translateY(0);color:var(--fg);border-color:var(--accent)}
.line{position:absolute;left:0;right:0;top:58%;height:2px;background:var(--accent-2);opacity:.5}
js
const vp = document.getElementById('vp');
const track = document.getElementById('track');
const io = new IntersectionObserver((entries) => {
  entries.forEach((en) => en.target.classList.toggle('in', en.isIntersecting));
}, { root: vp, threshold: 0.5 });
document.querySelectorAll('.row').forEach((r) => io.observe(r));

let y = 0;
function loop() {
  y -= 0.5;
  if (y < -track.scrollHeight / 2) y = 0;
  track.style.transform = 'translateY(' + y + 'px)';
  requestAnimationFrame(loop);
}
requestAnimationFrame(loop);

Instead of listening to scroll directly, IntersectionObserver watches only for the moment an element starts appearing. The callback just adds a class; a CSS transition does the rest.

It's common to observer.unobserve() an element once it's shown — re-triggering on every scroll pass is distracting, not delightful. A threshold (say 0.5) controls how much of the element must be visible before it fires.

Since a card preview can't be scrolled, this demo auto-scrolls a mock container upward and reveals each row as it crosses into view.

When to use

Use it to add delight to content that's already there, not to hide information — it gets in the way for slow scrollers and screen readers.