Blog / CSS/JS

Animate Text Letter by Letter with JavaScript

Master text splitting techniques to create spectacular text animations: letter-by-letter reveal, scramble effect, advanced typewriter and much more.

Introduction to Text Splitting

Text splitting is a fundamental technique for creating advanced text animations. The principle is simple: break text down into individual elements (letters, words, or lines) to animate them independently.

This technique is used by the greatest websites and creative agencies to create memorable experiences. From titles that reveal letter by letter to spectacular glitch effects, the possibilities are endless.

💡
Why use text splitting?

Animating an entire text only allows global transformations (fade, slide). By splitting the text, each character becomes an individually animatable element, opening the door to much richer effects.

In this complete guide, we will explore 6 different techniques of text splitting and animation, from the simplest to the most elaborate. Each example includes the complete code, ready to copy.

Split by letters with JavaScript

The first step is to create a function that will break our text into individual characters. Each letter will be wrapped in a <span> to be animated.

split-by-chars.js
function splitByChars(element) {
  // Save the original text
  const text = element.textContent;

  // Clear the element
  element.innerHTML = '';

  // Split each character
  text.split('').forEach((char, index) => {
    const span = document.createElement('span');
    span.className = 'char';

    // Space handling (uses non-breaking space)
    span.textContent = char === ' ' ? '\u00A0' : char;

    // Progressive delay for animation
    span.style.animationDelay = `${index * 0.05}s`;

    element.appendChild(span);
  });

  return element;
}

// Utilisation
const title = document.querySelector('.my-title');
splitByChars(title);

Key implementation points

  • Non-breaking spaces: Normal spaces would be ignored by the browser. We use \u00A0 to preserve them.
  • Animation delay: Each character receives a progressive delay (0.05s by default) to create the cascade effect.
  • .char class: Allows targeting all characters in CSS for animation.

Split by words

Sometimes, animating word by word is more suitable, particularly for long sentences or headings. The principle is similar, but splitting on spaces.

Welcome to Effects Lab
split-by-words.js
function splitByWords(element) {
  const text = element.textContent;
  const words = text.split(' ');

  element.innerHTML = '';

  words.forEach((word, index) => {
    const span = document.createElement('span');
    span.className = 'word';
    span.textContent = word;
    span.style.animationDelay = `${index * 0.15}s`;

    element.appendChild(span);
  });
}

// CSS associe
/*
.word {
  display: inline-block;
  margin: 0 8px;
  opacity: 0;
  transform: translateY(20px) rotateX(-90deg);
}

.animate .word {
  animation: wordReveal 0.6s forwards;
}

@keyframes wordReveal {
  to {
    opacity: 1;
    transform: translateY(0) rotateX(0);
  }
}
*/

Letter-by-letter animations

Once the text is split, different types of animations can be applied. Here are the 4 most popular animations with their interactive demos.

Fade In Stagger

The most classic animation: each letter appears progressively with a fade.

EFFECTS LAB

Slide Up

The letters appear rising from the bottom, creating a dynamic reveal effect.

SLIDE UP

Wave Effect

A looping animation where the letters ripple like a wave. Perfect for attracting attention.

WAVE

Scale Pop

Each letter appears with a zoom effect, as if it "pops" onto the screen.

POP!
animations.css
/* Base: each character is invisible by default */
.split-text .char {
  display: inline-block;
  opacity: 0;
}

/* 1. Fade In */
.split-text.animate-fade .char {
  animation: charFadeIn 0.5s forwards;
}

@keyframes charFadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* 2. Slide Up */
.split-text.animate-slide .char {
  animation: charSlideUp 0.5s forwards;
}

@keyframes charSlideUp {
  from {
    opacity: 0;
    transform: translateY(30px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* 3. Wave (continuous animation) */
.split-text.animate-wave .char {
  opacity: 1;
  animation: charWave 1.5s ease-in-out infinite;
}

@keyframes charWave {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-15px); }
}

/* 4. Scale Pop */
.split-text.animate-scale .char {
  animation: charScale 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
}

@keyframes charScale {
  from {
    opacity: 0;
    transform: scale(0);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

Scramble / Shuffle Effect

The scramble (or shuffle) effect is one of the most impressive: the text displays random characters that progressively stabilize to reveal the final text. It is the quintessential "hacker" effect.

DECRYPTING...
scramble-effect.js
class ScrambleText {
  constructor(element, options = {}) {
    this.element = element;
    this.chars = options.chars || '!@#$%^&*()_+-=[]{}|;:,.<>?';
    this.duration = options.duration || 2000;
    this.originalText = element.textContent;
  }

  scramble(newText) {
    const target = newText || this.originalText;
    const length = target.length;
    const startTime = Date.now();

    const animate = () => {
      const elapsed = Date.now() - startTime;
      const progress = Math.min(elapsed / this.duration, 1);

      // Number of revealed characters
      const revealed = Math.floor(progress * length);

      let result = '';
      for (let i = 0; i < length; i++) {
        if (i < revealed) {
          // Caractere final
          result += target[i];
        } else if (target[i] === ' ') {
          // Preserve spaces
          result += ' ';
        } else {
          // Caractere aleatoire
          result += this.chars[Math.floor(
            Math.random() * this.chars.length
          )];
        }
      }

      this.element.textContent = result;

      if (progress < 1) {
        requestAnimationFrame(animate);
      }
    };

    animate();
  }
}

// Utilisation
const scrambler = new ScrambleText(
  document.querySelector('.scramble-text'),
  { duration: 2000 }
);
scrambler.scramble('ACCESS GRANTED');
⚠️
Performance

The scramble effect updates the DOM every frame. For long character strings, consider limiting the framerate or using textContent instead of innerHTML for better performance.

Advanced Typewriter effect

The typewriter effect is a timeless classic. Here is an advanced implementation with a blinking cursor, deletion support, and the ability to chain multiple texts.

typewriter.js
class Typewriter {
  constructor(element, options = {}) {
    this.element = element;
    this.typeSpeed = options.typeSpeed || 100;
    this.deleteSpeed = options.deleteSpeed || 50;
    this.pauseTime = options.pauseTime || 2000;
    this.texts = options.texts || [];
    this.loop = options.loop !== undefined ? options.loop : true;
    this.currentIndex = 0;
    this.isRunning = false;
  }

  async type(text) {
    for (let i = 0; i <= text.length; i++) {
      this.element.textContent = text.slice(0, i);
      await this.sleep(this.typeSpeed);
    }
  }

  async delete(text) {
    for (let i = text.length; i >= 0; i--) {
      this.element.textContent = text.slice(0, i);
      await this.sleep(this.deleteSpeed);
    }
  }

  sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  async start() {
    this.isRunning = true;

    while (this.isRunning) {
      const text = this.texts[this.currentIndex];

      await this.type(text);
      await this.sleep(this.pauseTime);
      await this.delete(text);
      await this.sleep(500);

      this.currentIndex = (this.currentIndex + 1) % this.texts.length;

      if (!this.loop && this.currentIndex === 0) break;
    }
  }

  stop() {
    this.isRunning = false;
  }
}

// Utilisation
const typewriter = new Typewriter(
  document.querySelector('.typewriter-text'),
  {
    texts: ['Developer', 'Designer', 'Creator'],
    typeSpeed: 80,
    deleteSpeed: 40,
    pauseTime: 1500
  }
);
typewriter.start();

Recommended libraries

If you don't want to code these effects yourself, several excellent libraries can save you time.

Library Size Split Animations Note
SplitType ~5KB Via CSS/GSAP Most popular
GSAP SplitText ~10KB Premium, most powerful
Splitting.js ~2KB Via CSS Ultra-leger
Typed.js ~5KB Typewriter only Specialise typewriter

Example with SplitType

splittype-example.js
// Installation: npm install split-type
import SplitType from 'split-type';

// Split text into characters
const text = new SplitType('.my-title', {
  types: 'chars, words',
  tagName: 'span'
});

// Access the elements
console.log(text.chars);  // Array of character s
console.log(text.words);  // Array of word s

// With GSAP for animation
gsap.from(text.chars, {
  opacity: 0,
  y: 20,
  stagger: 0.05,
  duration: 0.5,
  ease: 'power2.out'
});
📚
Useful resources

Check out the SplitType documentation for more advanced options like line splitting or style preservation.

Best practices

Before deploying your text animations in production, keep these tips in mind.

Performance

  • Use transform and opacity for animations - these properties are GPU-optimized
  • Avoid animating too many elements simultaneously - more than 50 characters can slow down mobile devices
  • Use will-change sparingly on animated elements

Accessibility

  • Respect prefers-reduced-motion for motion-sensitive users
  • Do not block reading - the final text must be visible quickly
  • Keep the text accessible to screen readers
accessibility.css
/* Disable animations if user prefers */
@media (prefers-reduced-motion: reduce) {
  .split-text .char,
  .word-split .word {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
}

SEO

  • Text must be present in the initial HTML, not generated only by JS
  • Avoid replacing text with images or canvas
  • Spans do not affect SEO as long as the text content is preserved

Conclusion

Text splitting is a powerful technique that opens the door to spectacular text animations. Whether you choose to implement your own solution or use a library, the possibilities are endless.

We have covered the essential techniques:

  • Split by characters for letter-by-letter animations
  • Split by words for smoother reveals
  • CSS animations: fade, slide, wave, scale
  • Scramble effect for a "hacker" look
  • Typewriter for the typewriter effect

Do not hesitate to combine these techniques and experiment. The best animations are often those that surprise while remaining subtle.

🚀
Go further

Discover our effects library for ready-to-use text animations, with one-click copyable code.