TransitionsFree

Character Cascade

A word split into letters that rise 20 px one after another, 80 ms apart, then replaced by the next word every 2.5 s: the only cascade that works on the text itself — 2 KB of JavaScript, your words written in the HTML, read as one whole word by screen readers, letters placed at once when the user reduces motion.

JSTexteCascade

Updated

Hover or click the scene to interact

This effect is free — the Transitions category contains 52 effects total, including 9 free. Effect.Labs has 840 vanilla effects. Explore the category →

Usage examples

Tour of Morbihan · amateursLIVE

Next stage town

VANNESLORIENT

Stage 4 · 118 kmStart tomorrow 1 pm

① Next stage town — broadcast of an amateur cycling race

WhenAt the end of the stage, the broadcast banner announces the next town: "VANNES" fades, "LORIENT" writes itself letter by letter above the distance and start time.
WhyThe name written letter by letter announces the new stage like opening credits, with no video graphics.
SettingsLetters in 52 px, weight 900, italic, spaced 0.06 em, white on a 55% black band rimmed in yellow; original delay and cycle (80 ms, 2.5 s); words given as the container's children.
Lecture hall BLavoisier building

Next class

LAWCHEMISTRY

10:15 am – 12:15 pm · first-year students

Doors open 10 min before240 places

② Next hour's subject — screen at a lecture hall door

WhenAt the change of hour, the screen of lecture hall B switches from "LAW" to "CHEMISTRY", with the time and the class below; the student walking down the corridor sees the word being written.
WhyA single word to read from afar: the cascade catches the eye of the student arriving in the corridor, without flashing.
SettingsLetters in 64 px, weight 800, on a black screen rimmed in gray; "Next class" label in green above; original cycle; on the French page, the words are DROIT and CHIMIE.

BOOKSHOP · THE HIGH TIDE

Theme of the month

CRIMETRAVEL

Our booksellers' picks on the front table

③ Theme of the month — digital window of an independent bookshop

WhenOn the 1st of the month, the window screen switches from "CRIME" to "TRAVEL" above a row of book spines; passers-by see the word form as they come closer.
WhyThe word written letter by letter draws passers-by to the window, like a poster being painted.
SettingsLetters in Georgia 66 px, weight 700, black on cream paper; a row of book spines at the bottom; same delay and same cycle.

How it works

The .md-char-stage frame (250 px tall, background #1a1a24) centers .md-char-cascade. The script writes one letter per span.md-char into it: 2rem, weight 700, white, display: inline-block so the letter can move on its own. Each letter starts invisible and 20 px lower, and the mdCharIn animation lifts it in 0.4 s; forwards keeps it in place. The offset is written by the script: animation-delay is i × 0.08 s. A six-letter word is fully written in 0.8 s.

The script walks through every .md-char-cascade on the page. It reads its words from the container's children — one element per word, for example <span>VANNES</span><span>LORIENT</span> — and, if the container is empty, takes the demo list (MOTION, DESIGN, EFFECT, SMOOTH). showWord() empties the container, writes the whole word into a span.md-char-sr invisible to the eye, then the letters, each marked aria-hidden="true"; a space becomes a non-breaking space to keep its width. Every 2.5 s, the next word takes its place. An IntersectionObserver only runs this cycle while the block is visible; with a single word, there is no cycle, the cascade plays once.

The automatic cycle is the nature of the effect: a single word to read, changing at a steady pace, like a sign. Separate letters do not get in the way of assistive reading: measured in Chromium 151 and Firefox 153, the accessibility tree contains "MOTION" in one piece, not six letters. The frame has its own class, prefixed like the rest of the effect: two effects from the library pasted on the same page do not overwrite each other.

Accessibility

  • Reduced motion: under prefers-reduced-motion: reduce, the CSS puts each letter in place, with no rise (animation: none; opacity: 1; transform: none). The word keeps changing every 2.5 s, without motion. Measured in Chromium 151, Firefox 153 and WebKit 26: six letters at opacity 1 at the first measurement.
  • Screen readers: the whole word is written in a visually hidden span, and the animated letters are hidden from assistive technology. Measured: the accessibility tree gives "MOTION", then "DESIGN" 2.5 s later. The container has no aria-live: a change announced aloud every 2.5 s would be exhausting. If the word carries important information, also write it elsewhere on the page.
  • Auto-updating: content that changes by itself for more than 5 seconds must be pausable. For a display inside a reading page, add a button that stops the cycle (clearInterval), or limit the number of rounds; on a public display screen (room door, shop window), the cycle is expected.
  • Contrast: white letters on #1a1a24, over 15:1 once in place; during the 0.4 s rise, opacity grows from 0 to 1.

Browser compatibility

CSS (@keyframes, transform) + 2 KB of JavaScript (IntersectionObserver, setInterval), no dependency. Measured in Chromium 151, Firefox 153 and WebKit 26: same cascade, same word change at 2.5 s, same whole word exposed to screen readers, same immediate placement under reduced motion.

Chrome 51+✓ Full
Firefox 55+✓ Full
Safari 12.1+✓ Full
Edge 79+✓ Full
Mobile iOS 12.2+✓ Full
Android Chrome 51+✓ Full

Without IntersectionObserver, the cycle runs permanently. Without JavaScript, your words written in the container show as they are, side by side and without animation: keep only the first word in the HTML if the no-script display matters, and add the others from the script.

The code

Copy the three blocks into your page. No dependencies.

HTML
<div class="md-char-stage">
  <div class="md-char-cascade"></div>
</div>
CSS
.md-char-stage,
.md-char-stage *,
.md-char-stage *::before,
.md-char-stage *::after {
  box-sizing: border-box
}

.md-char-stage {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 250px;
  overflow: hidden;
  position: relative;
  background: rgb(26, 26, 36);
  color: rgb(255, 255, 255);
  font-family: sans-serif;
}

.md-char-cascade {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.md-char {
  display: inline-block;
  font-size: 2rem;
  font-weight: 700;
  color: #fff;
  opacity: 0;
  transform: translateY(20px);
  animation: mdCharIn 0.4s forwards;
}

@keyframes mdCharIn {
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* Le mot entier, lu par les lecteurs d'écran, invisible à l'œil */
.md-char-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Mouvement réduit : les lettres sont posées d'emblée, le mot change sans mouvement */
@media (prefers-reduced-motion: reduce) {
  .md-char {
    animation: none;
    opacity: 1;
    transform: none;
  }
}
JavaScript (fx-0731)
(function() {
  // Character Cascade : le mot monte lettre par lettre, puis cède la place au suivant toutes les 2,5 s
  document.querySelectorAll('.md-char-cascade').forEach(function(container) {
    // Les mots : un élément enfant par mot (<span>VANNES</span><span>LORIENT</span>),
    // sinon la liste de démonstration
    var words = Array.prototype.map.call(container.children, function(el) {
      return el.textContent.trim();
    }).filter(Boolean);
    if (!words.length) words = ['MOTION', 'DESIGN', 'EFFECT', 'SMOOTH'];
    var currentWord = 0;
    var intervalId = null;

    function showWord() {
      // Animations mises en pause (bouton Pause de la page, outils d'accessibilité) : on garde le mot affiché
      var first = container.querySelector('.md-char');
      if (first && getComputedStyle(first).animationPlayState === 'paused') return;
      container.innerHTML = '';
      var word = words[currentWord];
      // Le mot entier pour les lecteurs d'écran ; les lettres, masquées pour eux, sont pour l'œil
      var label = document.createElement('span');
      label.className = 'md-char-sr';
      label.textContent = word;
      container.appendChild(label);
      word.split('').forEach(function(char, i) {
        var span = document.createElement('span');
        span.className = 'md-char';
        span.setAttribute('aria-hidden', 'true');
        span.textContent = char === ' ' ? '\u00a0' : char;
        span.style.animationDelay = (i * 0.08) + 's';
        container.appendChild(span);
      });
      currentWord = (currentWord + 1) % words.length;
    }
    showWord();
    if (words.length < 2) return;

    // Le cycle ne tourne que lorsque le bloc est visible
    if ('IntersectionObserver' in window) {
      var obs = new IntersectionObserver(function(entries) {
        if (entries[0].isIntersecting) {
          if (!intervalId) intervalId = setInterval(showWord, 2500);
        } else {
          if (intervalId) {
            clearInterval(intervalId);
            intervalId = null;
          }
        }
      }, {
        threshold: 0
      });
      obs.observe(container);
    } else {
      // Repli : cycle permanent
      intervalId = setInterval(showWord, 2500);
    }
  });
})();

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Your words (HTML — children of .md-char-cascade) none: MOTION, DESIGN, EFFECT, SMOOTH One element per word, in display order: <span>LAW</span><span>CHEMISTRY</span>. A single word: the cascade plays once, with no cycle.
Gap between letters (JS — i * 0.08) 80 ms 40 ms for a long word (12 letters in 0.84 s); 120 ms for a typewriter feel. Keep the full writing under half the cycle.
Cycle pace (JS — setInterval(showWord, 2500)) 2.5 s per word 4 s for a display read from afar; below 1.5 s, the word has no time to be read after its cascade.
Rise and duration (CSS — translateY(20px), mdCharIn 0.4s) 20 px, 0.4 s translateY(0.6em) keeps the same proportion at any text size; rotateX(90deg) instead gives letters that flip up.
Typography (CSS — .md-char) 2rem, weight 700, white Any font works, each letter being an inline-block box. Ligatures and kerning are lost: prefer capitals or an evenly spaced font for large headings.

FAQ

Does a screen reader read the word or the letters?

The word. The script writes the whole word into a span.md-char-sr, hidden from the eye but read by assistive technology, and marks each animated letter aria-hidden="true". Measured in Chromium and Firefox: the accessibility tree contains "MOTION" in one piece. Keep the .md-char-sr CSS rule if you change the styling: without it, the word would show twice on screen.

How do I stop the cycle after one round?

In showWord(), after currentWord = (currentWord + 1) % words.length;, clear the interval when currentWord is back to 0: if (currentWord === 0) { clearInterval(intervalId); obs.disconnect(); }. The last word stays displayed. For a pause button, simply call clearInterval(intervalId).

What about a phrase of several words?

Spaces are kept (they become non-breaking spaces), so "BELLE ÎLE" is written correctly. But a line of inline-block letters does not wrap between words: beyond 15 to 20 characters, reduce the size or split the phrase into several containers, one per line.