Show text letter by letter

What is the most elegant way of showing an html text letter by letter (like videogame captions) using CSS and JavaScript?

While I'm sure that his can be solved using a brute-force approach (say, splitting the characters and print them one by one using jQuery.append()), I'm hoping there's some CSS3 (pseudo-elements?) or JQuery magic to do this more elegantly.

Extra points if the solution considers inner HTML content.


Solution 1:

HTML

<div id="msg"/>

Javascript

var showText = function (target, message, index, interval) {   
  if (index < message.length) {
    $(target).append(message[index++]);
    setTimeout(function () { showText(target, message, index, interval); }, interval);
  }
}

Call with:

$(function () {
  showText("#msg", "Hello, World!", 0, 500);   
});

Solution 2:

If a smooth reveal is reasonable then I think this should be pretty straightforward. Untested, but this is how I imagine it would work

html

<div id="text"><span>The intergalactic space agency</span></div>

css

div#text { width: 0px; height: 2em; white-space: nowrap; overflow: hidden;  }

jQuery

var spanWidth = $('#test span').width();
$('#text').animate( { width: spanWidth }, 1000 );

Okay, I couldn't resist and made a fiddle. One little code error that I fixed. Looks good to me though!

http://jsfiddle.net/mrtsherman/6qQrN/1/

Solution 3:

100% vanilla javascript, strict mode, unobtrusive html,

function printLetterByLetter(destination, message, speed){
    var i = 0;
    var interval = setInterval(function(){
        document.getElementById(destination).innerHTML += message.charAt(i);
        i++;
        if (i > message.length){
            clearInterval(interval);
        }
    }, speed);
}

printLetterByLetter("someElement", "Hello world, bonjour le monde.", 100);

Solution 4:

You really should just append, or show/hide.

However, if for some odd reason you don't want to alter your text, you can use this overly-complicated-for-no-good-reason piece of code:

HTML:

<p>I'm moving slowly...<span class="cover"></span></p>

CSS:

p {
    font-family: monospace;
    float: left;
    padding: 0;
    position: relative;
}
.cover {
    height: 100%;
    width: 100%;
    background: #fff;
    position: absolute;
    top: 0;
    right: 0;
}

jQuery:

var $p = $('p'),
    $cover = $('.cover'),
    width = $p.width(),
    decrement = width / $p.text().length;

function addChar()
{        
    $cover.css('width', '-=' + decrement);

    if ( parseInt( $cover.css('width') ) < width )
    {
        setTimeout(addChar, 300);
    }
}

addChar();

And finally, here's the fiddle: http://jsfiddle.net/dDGVH/236/

But, seriously, don't use this...

Solution 5:

Make your code more elegant by preparing promises for each iteration, then execute them as second step where you can inject DOM logic.

const message = 'Solution using Promises';

const typingPromises = (message, timeout) =>
  [...message].map(
    (ch, i) =>
      new Promise(resolve => {
        setTimeout(() => {
          resolve(message.substring(0, i + 1));
        }, timeout * i);
      })
  );

typingPromises(message, 140).forEach(promise => {
  promise.then(portion => {
    document.querySelector('p').innerHTML = portion;
  });
});
<div ><p></p></div>