Creating typewriter effect using JavaScript
In this tutorial, I'll be going over how I built the typewriter effect on one of my websites with a little bit of JavaScript and some tea in the afternoon.
There are quite a few tutorials, that go about achieving this. There are some very clever ones using Pure CSS and even an NPM package that provides this and much more.
However, I'm not very clever with CSS and I didn't want to add a few KB's to my bundle for a simple thing. Plus it's a bit of practice and some good fun to build one by myself.
Setup
First we create an HTML file with following bit of code.
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Typewriter Effect</title><link rel="stylesheet" href="/style.css" /><script src="/script.js"></script></head><body><div class="text"><span id="typewriter"></span><span class="cursor-blinking">|</span></div></body></html>
Just a basic template for us to get started with. #typewriter
is where our text would go and following that we have a blinking cursor. Let's add some CSS to have our cursor blinking.
.text {font-size: 4rem;}@keyframes blinking {0% {color: transparent;}50% {color: inherit;}100% {color: transparent;}}.cursor-blinking {animation: 1s blinking step-end infinite;}
This will :
- Render our text, nice and big.
- Register a
keyframe
animation that repeats every 1s infinitely switching text color fromtransparent
toinherit
giving us our blinking cursor.
JavaScripty Bit
I first wrote this code with a bunch of functions
and later refactored it into a class
.
class TypeWriter {constructor(words, element) {this.words = words;this.element = element;this.rafReference = null;}}
We will create a class
TypeWriter which would accept an array of words and a DOM Node element.
Now we need to loop over this array of words. So we'll create a method on our class called loop
.
loop(fn, dt) {let raf;let dateTime = dt;const currentTime = new Date().getTime();const delta = currentTime - dateTime;if (delta >= 250) {dateTime = currentTime;fn(raf);}this.rafReference = requestAnimationFrame(() => {this.loop(fn, dateTime);});}
Here, we're calculating delta
between our currentTime
and previous dateTime
and if delta
is more than 250ms
, we call our callback function.
Magic number 250 is the time it takes an average typist to type a character assuming an average person types 50-60 words per minute and an average word is 5-4 characters long. Adjust it as you see fit.
Using requestAnimationFrame
we can tell the browser to call a function before next repaint. we thus, recursively call loop
method with callback
and current dateTime
as arguments.
We'll now update our constructor :
constructor(words, element) {this.words = words;this.wordIdx = 0; //Current Word Indexthis.charIdx = 0; //Current character Indexthis.charIncrementor = 1; //Increment character by 1this.element = element;this.delay = 5;//Wait time it takes to type 5 chars at endthis.currWord = this.words[this.wordIdx]; //Current Wordthis.rafReference = null;}
Next we add an init
method to initialise animation and incrementCharacters
to type one character at a time.
incrementCharacters() {this.charIdx += this.charIncrementor;}
animate(word, end) {this.element.innerHTML = word.substring(0, end);}init(fn) {this.loop(() => {this.incrementCharacters(); //Increment characterIndex// Type character on the screen at a time.this.animate(this.words[this.wordIdx], this.charIdx);}, new Date().getTime());}
We would now like to wait sometime when we're done typing and then delete the characters we've typed on the screen.
if (this.charIdx === this.currWord.length + this.delay) {//Remove characters now that we've reached end of the word.this.charIncrementor = -1;}
We'd like to move onto next word when we're done with our current word. Hence,
nextWord() {this.wordIdx += 1;this.charIncrementor = 1;this.currWord = this.words[this.wordIdx];}if (this.charIdx === 0) {// When all characters of previous word have been hidden.this.nextWord();}
When we're done with last word, we'd like to start again in a loop with the first word in array.
reset() {this.wordIdx = 0;this.charIdx = 0;this.charIncrementor = 1;this.currWord = this.words[this.wordIdx];}if (this.wordIdx === this.words.length) {//Start again from first word now that we have reached end.this.reset();}
When using something like React, we'd like to clear all animations when component unmounts which is how I'm currently using this animation. We'll add a destroy
function. This is where our this.rafReference
will be required.
destroy() {cancelAnimationFrame(this.rafReference);}
Finally we'll update our HTML file.
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Typewriter Effect</title><link rel="stylesheet" href="/style.css" /><script src="/script.js"></script></head><body><div class="text"><span id="typewriter"></span><span class="cursor-blinking">|</span></div><script>const words = ['Bananas', 'Apples', 'Lorem Ipsum Dolor Sit Amet'];const typewriter = document.getElementById('typewriter');const writer = new TypeWriter(words, typewriter);writer.init();</script></body></html>
That's it folks!