# How to Create Text Animation Effects with Anime.js: Split Text and Character Animation Guide

> Create stunning text animation effects with Anime.js. Learn to split text into characters and animate them individually for dynamic web designs. Get started now!

- Repository: [Julian Garnier/anime](https://github.com/juliangarnier/anime)
- Tags: tutorial
- Published: 2026-03-04

---

**Use the `splitText` function to wrap text content into animatable `<span>` elements, then target the resulting character arrays with `animate` or `createTimeline` using staggered delays.**

The `anime` library by Julian Garnier provides a dedicated **text-splitting engine** that transforms static DOM text into hierarchical collections of lines, words, and individual characters. By isolating each character into its own wrapper element, you gain granular control over entrance effects, hover interactions, and complex kinetic typography sequences.

## Understanding the splitText API

The `splitText` factory function, located in [`src/text/split.js`](https://github.com/juliangarnier/anime/blob/main/src/text/split.js), processes a target element and returns a `TextSplitter` instance containing arrays of generated wrappers.

### Core Parameters and Target Selection

The function accepts a target selector, element, `NodeList`, or array, followed by a parameters object:

- **`chars`**: Boolean or template string enabling character-level splitting
- **`words`**: Boolean enabling word-level wrapping
- **`lines`**: Boolean enabling line detection based on layout
- **`clone`**: String identifier for creating duplicate elements (used in hover effects)
- **`wrap`**: String specifying overflow behavior (`'clip'` creates a clipping container)

### Template Generation and DOM Structure

The engine uses `generateTemplate` to construct HTML strings with `{value}` and `{i}` placeholders. When processing text nodes, `processHTMLTemplate` creates `DocumentFragment` instances and replaces placeholders with actual content:

```javascript
// Default character template generates:
// <span class="char" data-char="{i}">{value}</span>

const { chars } = splitText('h1', { 
  chars: '<span class="custom-char" data-index="{i}">{value}</span>' 
});

```

The splitter automatically assigns `data-char` attributes for stagger indexing and adds `aria-hidden="true"` to generated spans while preserving a visually-hidden copy of the original text for screen readers.

## Creating Character Animation Effects

Once text is split, the `chars` array contains references to every character span, ready for standard Anime.js animations.

### Basic Character Fade-In

Import `splitText` alongside `animate` and `stagger` to create entrance effects:

```javascript
import { animate, stagger, splitText } from 'animejs';

const { chars } = splitText('#title', { chars: true });

animate(chars, {
  opacity: [0, 1],
  translateY: ['100%', 0],
  duration: 500,
  ease: 'outQuad',
  delay: stagger(30) // 30ms offset per character using data-char attribute
});

```

### Staggered Animations with data-char Attributes

The `stagger` helper automatically reads the `data-char` index when you specify the `use` parameter. This enables complex directional staggering:

```javascript
animate(chars, {
  scale: [1.5, 1],
  opacity: [0, 1]
}, {
  delay: stagger(50, { 
    from: 'center',
    use: 'data-char' 
  })
});

```

## Advanced Text Splitting Techniques

The `splitText` engine supports sophisticated patterns for interactive and 3D typography.

### Cloning and Hover Effects

Use the `clone` parameter to duplicate characters for slide-reveal effects. In [`examples/text/hover-effects/index.js`](https://github.com/juliangarnier/anime/blob/main/examples/text/hover-effects/index.js), the implementation creates clipped containers that slide on pointer events:

```javascript
import { createScope, createTimeline, animate, stagger, splitText } from 'animejs';

createScope({
  root: '#hover-target',
  defaults: { ease: 'outQuad', duration: 500 }
}).add(scope => {
  const { root, methods } = scope;
  
  // Clone chars to the left side with clip overflow
  const { chars } = splitText('h2', {
    chars: { class: 'char', clone: 'left', wrap: 'clip' }
  });

  // Timeline slides the cloned inner span
  const tl = createTimeline({ autoplay: false })
    .add('.char > span', { 
      x: '100%' 
    }, stagger(5, { use: 'data-char' }));

  scope.add('onEnter', () => animate(tl, { progress: 1 }));
  scope.add('onLeave', () => animate(tl, { progress: 0 }));

  root.addEventListener('pointerenter', methods.onEnter);
  root.addEventListener('pointerleave', methods.onLeave);
});

```

### Dynamic Wave Animations

Combine `splitText` with timeline modifiers to create reactive wave effects that respond to mouse position:

```javascript
const { chars } = splitText('h2', { chars: true });

const wave = createTimeline({
  autoplay: false,
  loop: true,
  alternate: true
}).add(chars, {
  y: ['-50%', '50%'],
  duration: 500,
  ease: 'inOut(2)',
  modifier: v => v * strength // strength is animated externally
}, stagger(50));

// Animate strength parameter on hover
let strength = 0;
root.addEventListener('pointerenter', () => {
  animate({ strength: 1 }, { 
    onBegin: () => wave.play() 
  });
});

```

### Custom HTML Templates for 3D Effects

Override the default template to inject multiple faces for 3D rotations:

```javascript
splitText('h2', {
  chars: `<span class="char-3d" data-char="{i}">
           <span class="face front">{value}</span>
           <span class="face back">{value}</span>
         </span>`
});

```

The `{value}` placeholder receives the character content, while `{i}` receives the index. The generated `data-char` attribute enables standard stagger functionality even with complex nested structures.

## Accessibility and Performance Considerations

The `TextSplitter` class in [`src/text/split.js`](https://github.com/juliangarnier/anime/blob/main/src/text/split.js) implements several optimizations for production use.

**Accessibility Features:**
- Each split operation inserts a visually-hidden `<span>` containing the original HTML before the first line, preserving content for screen readers
- Generated wrapper elements receive `aria-hidden="true"` to prevent redundant announcements
- The original text remains in the DOM for SEO crawlers while the visual presentation uses the split structure

**Performance Optimizations:**
- A `ResizeObserver` monitors the target element's width; when layout changes occur, the splitter automatically re-splits the text while preserving active animation times through the `keepTime` wrapper
- The engine reuses a hidden `$splitTemplate` element to parse HTML templates efficiently
- Cleanup functions registered via `addEffect` automatically remove generated DOM elements when calling `revert()` or when the scope refreshes

## Summary

- **Use `splitText(target, { chars: true })`** to wrap individual characters in animatable `<span>` elements with automatic `data-char` indexing
- **Access generated elements** through the returned object's `chars`, `words`, and `lines` arrays for direct animation targeting
- **Apply staggered delays** with `stagger(delay, { use: 'data-char' })` to create sequential character animations without manual loop calculations
- **Implement hover effects** using the `clone` and `wrap` parameters to create clipped, duplicate characters for slide-reveal interactions
- **Customize markup** by passing HTML template strings with `{value}` and `{i}` placeholders to build 3D or multi-layer character effects
- **Ensure accessibility** by relying on the automatic `aria-hidden` and visually-hidden original text insertion performed by the splitter engine

## Frequently Asked Questions

### How does splitText handle international characters and emojis?

The `TextSplitter` class uses `Intl.Segmenter` with `granularity: 'grapheme'` when available to correctly identify user-perceived characters, ensuring that multi-codepoint emojis and accented characters (like é as e + combining acute) are treated as single animatable units. If `Intl.Segmenter` is unsupported, the engine falls back to spreading the string with `[...str]`, which handles most Unicode cases in modern JavaScript environments.

### Can I animate words instead of individual characters?

Yes, set `words: true` in the parameters object to generate wrappers around whole words while leaving characters unsplit, or combine `words: true` with `chars: true` to create a hierarchy of line > word > character elements. The returned object contains separate `words` and `chars` arrays, allowing you to animate words with one timeline and characters with another for layered effects.

### How do I clean up splitText animations when the component unmounts?

The `splitText` function automatically registers cleanup logic with the current `Scope` via `addEffect`, ensuring that generated DOM elements, event listeners, and the `ResizeObserver` are removed when you call `scope.revert()` or when the scope refreshes. For manual cleanup outside a scope, store the splitter instance and call any returned revert functions, or simply remove the target element from the DOM, as the splitter does not maintain external references that would cause memory leaks.

### What is the performance impact of splitting large text blocks?

The initial split operation runs a `wordSegmenter` and `graphemeSegmenter` over the text content and creates DOM elements for each unit, which has O(n) complexity relative to character count; however, the engine minimizes reflows by building `DocumentFragment` objects before insertion and uses a `ResizeObserver` to avoid unnecessary re-splits during animations. For very large documents (thousands of characters), consider splitting only visible viewport text or using `words: true` instead of `chars: true` to reduce DOM node count.