DEV Community

Cover image for Simple typing effect pure JS (beginner)
ljc-dev
ljc-dev

Posted on • Updated on

Simple typing effect pure JS (beginner)

The typing effect is a simple yet stylish animation used by many bloggers and web developers to introduce themselves in an elegant fashion.

This tutorial will show you how to create the typing effect with plain css and js (no libraries).

Let's start by creating a text holder p with a class of typeText.

    <p class="typeText"></p>
Enter fullscreen mode Exit fullscreen mode

We add a blinking cursor after our paragraph with a CSS animation:

.typeText::after {
  content: "|";
  animation: blink 1s infinite;
}

@keyframes blink {
  0%, 100% {opacity: 1;}
  50% {opacity: 0;}
}
Enter fullscreen mode Exit fullscreen mode

The pseudo class ::after is going to add the cursor | and make it blink with the keyframes animation blink.

If you are not familiar with animations, I made an introduction to CSS animations here.

Here's all the js to the typing effect:

var typeText = document.querySelector(".typeText")
var textToBeTyped = "a software engineer"
var index = 0, isAdding = true

function playAnim() {
  setTimeout(function () {
    // set the text of typeText to a substring of
    // the textToBeTyped using index.
    typeText.innerText = textToBeTyped.slice(0, index)
    if (isAdding) {
      // adding text
      if (index > textToBeTyped.length) {
        // no more text to add
        isAdding = false
        //break: wait 2s before playing again
        setTimeout( function () {
          playAnim()
        }, 2000)
        return
      } else {
        // increment index by 1
        index++
      }
    } else {
      // removing text
      if (index === 0) {
        // no more text to remove
        isAdding = true
      } else {
        // decrement index by 1
        index--
      }
    }
    // call itself
    playAnim()
  }, 120)
}
// start animation
playAnim()
Enter fullscreen mode Exit fullscreen mode

The playAnim() function calls itself repeatedly using setTimeout with a delay of 120ms.

By using the string method slice(0, index) we set the text of typeText to a substring of the textToBeTyped.

typeText.innerText = textToBeTyped.slice(0, index)
Enter fullscreen mode Exit fullscreen mode

index begins at 0 and increments by 1 when we are adding and decrements by 1 when we are removing.

isAdding is a boolean that is used to check whether we are adding or removing.

When index is larger than the length of textToBeTyped, we set isAdding to false to start removing. And when index is equal to 0, we set isAdding is true to start adding again.

After it has finished adding, a setTimeout with a 2000ms delay will create a break for people to read the text before it gets removed again.

if (index > textToBeTyped.length) {
  // no more text to add
  isAdding = false
  //break: wait 2s before playing again
  setTimeout( function () {
    playAnim()
  }, 2000)
  return
}
Enter fullscreen mode Exit fullscreen mode

And we get:

And that's it! You should now be able to make your own typing effect.

I've tried to make it beginner friendly but I think my explanations weren't that great 😅. If you have questions, please leave a comment or hit me up on my socials 😁.

Beyond the basics

The cursor animation can be made more realistic by adding a built-in animation-timing-function step-end to our blink animation like this:

.typeText::after {
  content: "|";
  /* animation: blink 1s infinite; */
  animation: blink 1s step-end infinite;
}
Enter fullscreen mode Exit fullscreen mode

steps are a recent addition to css. They break the flow of an animation by playing it in jumping steps. The only difference between the two animations below is the addition of steps on the 2nd div.

We can increase the removing speed by changing the setTimeout delay with the boolean isAdding and a ternary operator like this:

function playAnim() {
  setTimeout(function () {
    // ...
  }, isAdding ? 120 : 60)
Enter fullscreen mode Exit fullscreen mode

The ternary operator means if it's adding set the delay to 120ms. If it's removing set the delay to 60ms.

And instead of typing one string we can choose an array of string textToBeTypedArr to be typed in turn. And a textToBeTypedIndex variable to keep track of the current text index in the array. textToBeTypedIndex will be updated it each time after we are done removing the previous text.

var typeText = document.querySelector(".typeText")
// var textToBeTyped = "a software engineer"
var textToBeTypedArr = ["a software engineer", "a warlord", "a king", "a peasant"]
var index = 0, isAdding = true, textToBeTypedIndex = 0

function playAnim() {
  setTimeout(function () {
    // set the text of typeText to a substring of the text to be typed using index.
    typeText.innerText = textToBeTypedArr[textToBeTypedIndex].slice(0, index)
    if (isAdding) {
      // adding text
      if (index > textToBeTypedArr[textToBeTypedIndex].length) {
        //...
      } else {
        //...
      }
    } else {
      // removing text
      if (index === 0) {
        //...
        //switch to next text in text array
        textToBeTypedIndex = (textToBeTypedIndex + 1) % textToBeTypedArr.length
      } else {
        //...
      }
    }
    // calls itself
    playAnim()
  }, isAdding ? 120 : 60)
}
// start animation
playAnim()
Enter fullscreen mode Exit fullscreen mode

And for prefectionists that don't like the cursor blinking when the text is being added/removed, we can toggle the blink animation by removing the animation from ::after and adding it only in js with the showAnim class:

.typeText::after {
  content: "|";
  /* animation: blink 1s step-end infinite; */
  animation: none;
}

.showAnim.typeText::after {
  animation: blink 1s step-end infinite;
}
Enter fullscreen mode Exit fullscreen mode

And toggling the showAnim class in js:

function playAnim() {
    //...
      if (index > textToBeTyped.length) {
        // no more text to add
        isAdding = false
        //break: wait 2s before playing again
        // play cursor blink animation
        typeText.classList.add("showAnim")
        setTimeout(function () {
          // remove cursor blink animation
          typeText.classList.remove("showAnim")
          playAnim()
        }, 2000)
        return
    //...
}
Enter fullscreen mode Exit fullscreen mode

End!

Latest comments (6)

Collapse
 
abdessattar profile image
Abdessattar Elyagoubi • Edited

Hello, Actually I want to ask you on how to make if we have a <a> tag, because your example can't render HTML tags

Collapse
 
victansitthi profile image
VicTansitthi

Looking very nice!

But I have question..

How do you stop de loop after it is finished?

Collapse
 
favouritejome profile image
Favourite Jome

Wow, this is really good. Recursion I don't really understand.
Thanks for sharing and for not using GSAP, I totally wouldn't have gotten anything then 😁

Collapse
 
ljcdev profile image
ljc-dev

Thx a lot Jome 😆! I'm not good at explaining recursion but I've learnt most of it through this article.
codeburst.io/learn-and-understand-...

GSAP is fun 😄. Will try to post on it when I learn it better.

Collapse
 
pentacular profile image
pentacular

The recursiveness of addLetter is interesting.

It's clearly following a recursive flow mediated by a scheduler.

It does not support backtracking, but that's not a requirement.

The main objection I have is that it isn't expressed as a reursive operation.

It should be addLetters, which calls addLetter(first), and schedules addLetters(rest), or terminates.

This would express the recursive decomposition of the task. :)

Collapse
 
ljcdev profile image
ljc-dev

Oh I see what u mean. I've updated the post entirely since but probably haven't made the changes u wanted. I get how addLetter should not be the one that handles the logic behind whether to add or not but I do think this keeps things easier to understand to those who dunno much about recursion. Thanks a lot for the feedback, much appreciated!!