16 CSS Fade In Animation Designs 10 / 16

Word-by-Word Split Fade

A headline is split word-by-word into spans via JavaScript, each assigned an animation-delay, creating a sequential typewriter-style word cascade with translateY spring bounce.

CSS + JS MIT licensed
Live Demo Open in tab
Open in playground

The code

<div class="fi-10" id="fi-10-root">
  <div class="fi-10__output">
    <div class="fi-10__label" id="fi-10-label">Word-by-word fade</div>
    <div class="fi-10__text" id="fi-10-text">Words appear one by one creating a cinematic reading experience</div>
    <div class="fi-10__sub" id="fi-10-sub">JavaScript splits text into spans, staggering animation-delay per word index.</div>
    <div class="fi-10__replay" id="fi-10-replay">↺ Replay</div>
  </div>
</div>
.fi-10{
  --bg:#0a0f0a;--green:#4ade80;--lime:#a3e635;--text:#f0fdf4;
  font-family:'Plus Jakarta Sans',sans-serif;
  min-height:320px;border-radius:20px;
  display:grid;place-items:center;
  padding:40px;overflow:hidden;
}
.fi-10 *,.fi-10 *::before,.fi-10 *::after{box-sizing:border-box;margin:0;padding:0}
.fi-10 ::selection{background:var(--green);color:#000}

.fi-10__output{text-align:center;max-width:560px}
.fi-10__label{
  font-size:.7rem;font-weight:700;letter-spacing:.2em;text-transform:uppercase;
  color:var(--green);margin-bottom:20px;opacity:0;
  animation:fi-10-word-fade .5s ease forwards .1s;
}
.fi-10__text{
  font-size:clamp(1.4rem,3.5vw,2.2rem);font-weight:800;line-height:1.4;
  color:var(--text);margin-bottom:24px;
}
/* JS injects spans; each span animates individually */
.fi-10__word{
  display:inline-block;margin-right:.3em;
  opacity:0;transform:translateY(12px);
  animation:fi-10-word-up .5s cubic-bezier(.16,1,.3,1) forwards;
}
.fi-10__sub{
  font-size:.9rem;color:rgba(240,253,244,.45);line-height:1.6;
  opacity:0;animation:fi-10-word-fade .5s ease forwards;
}
.fi-10__replay{
  margin-top:20px;display:inline-block;
  font-size:.75rem;font-weight:600;color:var(--green);cursor:pointer;
  border:1px solid rgba(74,222,128,.2);border-radius:20px;padding:6px 16px;
  background:rgba(74,222,128,.08);transition:background .2s;
}
.fi-10__replay:hover{background:rgba(74,222,128,.15)}

@keyframes fi-10-word-up{to{opacity:1;transform:translateY(0)}}
@keyframes fi-10-word-fade{to{opacity:1}}
@media(prefers-reduced-motion:reduce){
  .fi-10 *{animation:none!important;opacity:1!important;transform:none!important}
}
(function(){
  const textEl = document.getElementById('fi-10-text');
  const subEl  = document.getElementById('fi-10-sub');
  const replay = document.getElementById('fi-10-replay');
  const BASE_DELAY = 0.3;
  const STEP = 0.09;

  function splitAndAnimate(el, startDelay){
    const words = el.textContent.trim().split(/\s+/);
    el.innerHTML = '';
    words.forEach((w,i)=>{
      const span = document.createElement('span');
      span.className='fi-10__word';
      span.textContent = w;
      span.style.animationDelay = (startDelay + i * STEP)+'s';
      el.appendChild(span);
    });
    return startDelay + words.length * STEP;
  }

  function run(){
    const subDelay = splitAndAnimate(textEl, BASE_DELAY);
    subEl.style.animationDelay = (subDelay + 0.1)+'s';
    subEl.style.animation='none';
    void subEl.offsetWidth;
    subEl.style.animation='fi-10-word-fade .5s ease forwards';
  }

  run();

  replay.addEventListener('click',()=>{
    // Reset and re-animate
    textEl.querySelectorAll('.fi-10__word').forEach(s=>{
      s.style.animation='none';
      void s.offsetWidth;
      s.style.animation='fi-10-word-up .5s cubic-bezier(.16,1,.3,1) forwards';
    });
    subEl.style.animation='none';
    void subEl.offsetWidth;
    subEl.style.animation='fi-10-word-fade .5s ease forwards';
  });
})();

How this works

The JavaScript splitAndAnimate function reads the headline's text content, splits it on whitespace, and rebuilds the element as a sequence of span.fi-10__word elements. Each span gets an animation-delay of BASE_DELAY + index × STEP — 0.3s base, 0.09s per word — and the CSS keyframe fi-10-word-up moves them from translateY(12px) opacity:0 to resting position.

The subtitle carries a separate fade-only animation delayed until after the last word has appeared, so the two text regions sequence naturally. The replay handler iterates all word spans, forces a reflow with offsetWidth to flush the running animation, then reassigns the animation property — a clean restart without DOM rebuilding. Without JavaScript, the raw text remains visible as a static fallback.

Customize

  • Reduce STEP from 0.09 to 0.05s for faster word cascade — good for short headlines.
  • Change the keyframe to translateX(-16px → 0) for a horizontal word-slide instead of vertical drop.
  • Apply the same technique to a paragraph's sentences by splitting on . instead of whitespace.
  • Add a highlight colour to alternate words via nth-child CSS on .fi-10__word spans.

Watch out for

  • Splitting text into spans changes the DOM structure — any existing CSS targeting direct text nodes (e.g. first-line pseudo-element) will no longer match.
  • Long headlines with many words accumulate large total delays — cap the stagger at ~15 words to prevent the last word appearing after 1.5s+, which reads as a broken animation.
  • The offsetWidth trick to force reflow for animation restart works in all browsers but causes a synchronous layout — avoid calling it inside a rapid loop or animation frame.

Browser support

ChromeSafariFirefoxEdge
60+ 12+ 60+ 60+

DOM manipulation and animation restart pattern works in all modern browsers

Search CodeFronts

Loading…