Animating Text

We start by defining an empty <span/> in the body of your html document. This is where we’ll make our typing text appear.

<span id="animatedText"></span>

Next we define a function animateText() that takes two arguments, the text we’d like to see typed out, and the ID of the span element we created earlier. For now, the function will do nothing.

function animateText(text, elementId) {

}

We’ll need to get a handle on the span element. People used to rely on JQuery for this sort of thing, but now you can use one of the standard methods available in the browser document.getElementById.

function animateText(text, elementId) {
    const target = document.getElementById(elementId)
}

Now that we have a handle on our span element, we are able to programatically fill the span with text of our choosing like this

target.innerText = "Hello!"

This will come in handy later.

Nows its time to set up our animation itself.

Animating with javascript is typically done by making recursive calls to a built-in method called requestAnimationFrame

When you call requestAnimationFrame, you provide it with a callback function of your choice. By calling requestAnimationFrame(callbackFn) you’re saying

“Hey browser, the next time you’re about to redraw whats on the screen, please call this callback function first”.

Typically browsers redraw their screens pretty often, around 60 times a second.

We’ll call our callback function onScreenAboutToRedraw(). To begin with, we’ll make it empty i.e. do nothing.

function animateText(text, elementId) {
    const target = document.getElementById(elementId)

    function onScreenAboutToRedraw() {

    }
    onRequestAnimation(onScreenAboutToRedraw)
}

When onScreenAboutToRedraw is called, we want to calculate how much time has elapsed since the animation began; and thus how many characters of the text should be on the screen already.

To keep track of how much time has elapsed, we’ll need two pieces of information

  • a timestamp representing the start of our animation,
  • a timestamp representing when our callback function was run.

A timestamp representing the start of the animation can be obtained by calling the built-in function performance.now() at the beginning of our animateText().

function animateText(text, elementId) {
    const target = document.getElementById(elementId)
    const startTimeMs = performance.now();

    function onScreenAboutToRedraw() {

    }
    onRequestAnimation(onScreenAboutToRedraw)
}

To get a timestamp representing the time that the callback function ran is easy because according to the specification of onRequestAnimation, the browser actually calls callbackFn with the current time in milliseconds as a first arguement.

function animateText(text, elementId) {
    const target = document.getElementById(elementId)
    const startTimeMs = performance.now();

    function onScreenAboutToRedraw(currentTimeMs) {
        const timeElapsedMs = currentTimeMs - startTimeMs;
    }
    onRequestAnimation(onScreenAboutToRedraw)
}

Now we know how much time has elapsed, we need to calculate how many characters from our text we want to display.

We’ll do that by first deciding how long it should take to type each character. We’ll store that as a constant at the top of our javascript.

const TYPING_SPEED_MS_PER_CHAR = 45;

We then make the following calculation in our onScreenAboutToRedraw callback function

const numCharactersToShow = timeElapsedMs/TYPING_SPEED_MS_PER_CHAR

This will actually return an imperfect number like 3.234234 so we’ll round it down using Math.floor to get

const numCharactersToShow = Math.floor(timeElapsedMs/TYPING_SPEED_MS_PER_CHAR)

Next, we’ll use the built-in string method substr to pluck out the first numCharactersToShow from the original text.

const charactersToShow = text.substr(0, numCharactersToShow);

Finally we’ll update the contents of the span with that innerText we saw earlier.

target.innerText = charactersToShow;

Last but not least, we need to decide if we should continue the animation or not.

We should keep animating if we haven’t drawn all the characters on the screen yet.

To keep animating, we request another animation frame.

To stop animating, we do nothing further and return from our callbackFunction instead.

    if (numCharactersToShow >= text.length) {
        // All the characters are revealed.
        // No need to continue animating.
        return
    } else {
        // Ask the browser to call onAnimationFrame the next time its about to redraw the screen
        requestAnimationFrame(onScreenAboutToRedraw);
    }

Putting it all together

const TYPING_SPEED_MS_PER_CHAR = 45;

function animateText(text, elementId) {
    const target = document.getElementById(elementId)
    const startTimeMs = performance.now();

    // The browser will call this method just before it repaints the screen
    function onScreenAboutToRedraw(currentTimeMs) {
      const timeElapsedMs = currentTimeMs - startTimeMs;

      const numCharactersToShow = Math.floor(timeElapsedMs/TYPING_SPEED_MS_PER_CHAR);

      const charactersToShow = text.substr(0, numCharactersToShow);
      target.innerText = charactersToShow;

      if (numCharactersToShow >= text.length) {
        // All the characters are revealed.
        // No need to continue animating.
        return
      } else {
        // Ask the browser to call onAnimationFrame the next time its about to redraw the screen
        requestAnimationFrame(onScreenAboutToRedraw);
      }
    }

    // Ask the browser to call onAnimationFrame the next time its about to redraw the screen
    requestAnimationFrame(onScreenAboutToRedraw);
  }

  animateText(
    "Lets try this out! Wahoo it seems to work",
    "part1"
  );

Going Further

What we’ve built today has a simple amount of functionality. In the next tutorial, I’ll show you how we can evolve what we’ve built and turn it into something a lot more advanced.

Want to talk about this post? Find me on