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.
Published
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><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;
}
}.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';
});
})();(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';
});
})();Here's a working CSS Fade In Animation Design from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Word-by-Word Split Fade
Source: https://codefronts.com/motion/css-fade-in-animation/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.
## HTML
```html
<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>
```
## CSS
```css
.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;
}
}
```
## JavaScript
```js
(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';
});
})();
```Here's a working CSS Fade In Animation Design from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Word-by-Word Split Fade
Source: https://codefronts.com/motion/css-fade-in-animation/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.
## HTML
```html
<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>
```
## CSS
```css
.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;
}
}
```
## JavaScript
```js
(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.
Make it yours
- Reduce
STEPfrom0.09to0.05sfor 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-childCSS on.fi-10__wordspans.
Gotchas — read before shipping
- Splitting text into spans changes the DOM structure — any existing CSS targeting direct text nodes (e.g.
first-linepseudo-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
offsetWidthtrick 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
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 60+ | 12+ | 60+ | 60+ |
DOM manipulation and animation restart pattern works in all modern browsers