CSS Character Animation

Full source sample:

CSS provides the first-character and last-character selectors, but no nth-character selector. So, if we want to use CSS to animate text character-by-character, we have to split the text into selectable elements. <span> is a good choice:


You can do this by hand, but it looks very messy and is not maintainable. If your pages are dynamic, you could do the splitting on the back-end. I was dealing with static pages so I used some JavaScript to split the text content of an element:

// splits each character of an element's text into a span element
function spanSplitElementText(elem) {  
    var text = elem.innerText || elem.textContent;
    text = text.trim().replace(/ /g, "\u00a0"); // &nbsp;

    // remove children
    while (elem.firstChild) {

    var spans = [];

    // create children
    for (var i = 0; i < text.length; ++i) {
        var span = document.createElement("span");


    return spans;

Now that our text is split into elements, we can select each character individually. I wrote a small library for animating a group of elements in sequence:

// represents a group of elements to animate in sequence
function SeqAnimationGroup(elems, startDelayMS, minElemDelayMS, maxElemDelayMS) {  
    var DEFAULT_ELEM_DELAY  = 25;

    this.elems          = elems
    this.startDelayMS   = startDelayMS   || DEFAULT_START_DELAY;
    this.minElemDelayMS = minElemDelayMS || DEFAULT_ELEM_DELAY;
    this.maxElemDelayMS = maxElemDelayMS || minElemDelayMS;

// sequence animation
// animateFN(elem, delayMS)
//   elem    - Element to animate
//   delayMS - delay in milliseconds before this element should start animating
function SeqAnimation(animateFn) {  
    this.animate = function(groups) {
        var delayMS = 0;

        for (var i = 0; i < groups.length; ++i) {
            delayMS += groups[i].startDelayMS;

            for (var j = 0; j < groups[i].elems.length; ++j) {
                delayMS += groups[i].minElemDelayMS + Math.floor((groups[i].maxElemDelayMS - groups[i].minElemDelayMS) * Math.random());

                animateFn(groups[i].elems[j], delayMS);

Using this library you can create groups of elements that you want to animate in sequence (like the <span>-s we created earlier). You can then create a sequence animation with an animate function that applies the CSS animation. The animate function should accept an element as the first parameter and the delay (in milliseconds) as the second parameter.

I use this library on my home page to fade in each character of the description.


<span id="desc1" class="anim-group">Projects and programing shenanigans.</span>  
<span id="desc2" class="anim-group">Also beer.</span>  


.anim-group > span {
    opacity: 0.0;


var desc1 = document.getElementById("desc1");  
var desc2 = document.getElementById("desc2");

// split the text into spans
var spans1 = spanSplitElementText(desc1);  
var spans2 = spanSplitElementText(desc2);

// create the animation
var fadeInAnimation = new SeqAnimation(function(elem, delayMS) {  
    // animate to 100% visible over 250ms after an inital delay
    elem.style.opacity    = "1";
    elem.style.transition = "all 250ms ease-in-out " + delayMS + "ms";

// animate our description
// first sentence will start animating after 500ms with a delay of 25ms between each character
// second sequence will start 1500ms after the first squence is finished with a delay of 10ms between each character
  new SeqAnimationGroup(spans1, 500,  25),
  new SeqAnimationGroup(spans2, 1500, 10)

desc1 and desc2 contain the text I want to animate. spanElement splits the text, putting each character inside of a <span> tag. The CSS sets the initial visibility of each character to 0%. Our animation fades in each character by overriding the CSS class with the element's style. The element's style tells the browser to animate the visibility to 100% over 250ms after a set delay.

The code is browser friendly. If the browser doesn't support JavaScript (or it is disabled) or the opacity property is not supported, the text will simply remain static. If the translation property is not supported, the text will appear all at once.

I do some additional fancy stuff on the home page. I use a cookie so the animation only plays if you haven't visited the page in an hour. I also implement a couple fixes to keep the text from flashing visible when the page loads and some additional browser compatibility.

You can see the full code here or by viewing the source of the home page:

Mike Moore

Read more posts by this author.

Austin, TX yo1.dog
comments powered by Disqus