DEV Community

Cover image for Typewriter
Phuoc Nguyen
Phuoc Nguyen

Posted on • Originally published at phuoc.ng

Typewriter

The typewriter effect is a cool animation that makes text appear as if it's being typed on a page. It's a great way to add some pizzazz to your website and keep your visitors engaged.

There are tons of ways to use the typewriter effect to enhance your website's design and user experience. For instance, you can use it to introduce key concepts or features on your homepage, or to emphasize important text throughout your site.

In this post, we'll explore different ways to create a typewriter effect using pure CSS or JavaScript-based solutions. For demonstration purposes, we'll be using the following quotes.

  • Learning never exhausts the mind — Leonardo da Vinci
  • You teach best what you most need to learn — Richard Bach
  • In youth we learn; in age we understand — Marie von Ebner-Eschenbach

Markup

To get started with the typewriter effect, you'll need to create the HTML structure first. This layout has two basic elements: the container and the inner element. The outer element, which will have styles added later, holds everything together. The inner element is where the text will be displayed.

<div class="container">
  <div class="typewriter"></div>
</div>
Enter fullscreen mode Exit fullscreen mode

Using the steps() function

There are two approaches to creating a typewriter effect using CSS and JavaScript. The first approach is a CSS-only solution, which relies solely on CSS animation, without using any JavaScript code.

To create the typewriter effect, we increase the width of the inner element from 0% to 100% using a keyframe called typing which modifies the width property.

@keyframes typing {
    from {
        width: 0%;
    }
    to {
        width: 100%;
    }
}
Enter fullscreen mode Exit fullscreen mode

We start with 0% width, meaning that no characters are typed. At the end of the animation, the element width is set to its original, full width, meaning that all characters are typed completely.

The challenge is to indicate that, at each step of the animation duration timeline, a single character appears in the correct order from left to right. Fortunately, CSS provides the step() timing function for that purpose. We pass the number of steps to the function, and the animation performs the corresponding total of steps.

In our example, the total number of steps is the same as the total number of characters. If we use the effect for the quote Learning never exhausts the mind, then the number of steps is 48.

console.log('Learning never exhausts the mind'.length); // 48
Enter fullscreen mode Exit fullscreen mode

The animation declaration could look like this:

.typewritter {
    animation: typing 4s steps(48);
}
Enter fullscreen mode Exit fullscreen mode

It runs the typing animation over a duration of 4 seconds, in 48 equal steps. The animation constantly updates the width, so we need to add some additional styles to the inner element to ensure that the content doesn't overflow.

.container {
    width: fit-content;
}
.typewritter {
    overflow: hidden;
    white-space: nowrap;
}
Enter fullscreen mode Exit fullscreen mode

Blinking the cursor

To make the effect of typing even more realistic, we want to add a blinking cursor at the end of the text. There are a few ways to do this, but in this approach, we can use the right border of the text element instead of creating a new element just for the cursor.

First, we make the right border of the text transparent. Then, we animate the color of the border to a darker shade at the end of the animation to create the blinking effect.

.typewritter {
    border-right: 4px solid transparent;
}
@keyframes blink {
    from {
        border-right-color: transparent;
    }
    to {
        border-right-color: rgb(15 23 42);
    }
}
Enter fullscreen mode Exit fullscreen mode

Now is the perfect time to combine animations with text using CSS. It's easy as pie! Simply separate the animations with commas.

For example, in the code below, the text element runs two animations simultaneously. The first animation creates a typing effect that lasts for 4 seconds. The second animation creates a blinking cursor that fades in and out repeatedly.

.typewritter {
    animation: typing 4s steps(48), blink 1s infinite;
}
Enter fullscreen mode Exit fullscreen mode

Let's review the progress of the steps we've taken so far.

To make the cursor more customizable, you'll need to create a new element for it. We'll call it the cursor element and add it to the container.

<div class="container">
    <div class="typewritter">Learning never exhausts the mind</div>
    <div class="cursor">|</div>
</div>
Enter fullscreen mode Exit fullscreen mode

Since the text and cursor elements are aligned horizontally, we'll use CSS flexbox for the container.

.container {
    display: flex;
    align-items: center;
}
Enter fullscreen mode Exit fullscreen mode

To create the blinking effect, you can change the background color of the cursor over time. The animation runs on the cursor element, not the text element as in the previous section. Keep in mind that the cursor has a transparent text color, so the | character is invisible.

.cursor {
    background-color: rgb(15 23 42);
    color: transparent;
    width: 4px;

    animation: blink 1s infinite;
}
@keyframes blink {
    0%, 100% {
        background-color: transparent;
    }
    50% {
        background-color: rgb(15 23 42);
    }
}
Enter fullscreen mode Exit fullscreen mode

Check out these animations in action:

While the steps() function can create a smooth animation, it does have a downside - we need to know the total number of steps beforehand. This means that if we want to animate different text, we have to update the CSS code, which makes the code less reusable.

Unfortunately, this approach doesn't work with dynamic text. To solve this issue, let's move on to the next section.

Updating text dynamically

In this section, we'll explore another approach to updating text dynamically using JavaScript. By using the setTimeout() function and a little bit of code, we can display one character at a time.

To achieve this effect, we'll define a function called type that is called every 100 milliseconds. The function checks if the last character has been displayed, and if not, it displays the next character.

The type function uses a variable called charIndex to track the index of the last displayed character. We then check if this index is less than the length of the text. If it is, we display the next character using the charAt() function and increase the index by one.

const content = 'Learning never exhausts the mind';
const textEle = document.getElementById('text');

let charIndex = 0;
const type = () => {
    if (charIndex < content.length) {
        textEle.textContent += content.charAt(charIndex);
        charIndex++;
        setTimeout(type, 100);
    }
};
Enter fullscreen mode Exit fullscreen mode

To begin the effect, simply call the type function.

type();
Enter fullscreen mode Exit fullscreen mode

In reality, the cursor only blinks when we stop typing. But we can change that by moving the blinking animation to a separate class.

.cursor__blink {
    animation: blink 1s infinite;
}
Enter fullscreen mode Exit fullscreen mode

This class will be added or removed from the cursor element depending on whether the text has been fully displayed or not.

To make this happen, we can add some logic to the type() function. In the following example, we'll dynamically manage the blinking class by using the remove() and add() functions.

const cursorEle = document.getElementById('cursor');

const type = () => {
    if (charIndex < content.length) {
        cursorEle.classList.remove('cursor__blink');
        // ...
    } else {
        cursorEle.classList.add('cursor__blink');
    }
};
Enter fullscreen mode Exit fullscreen mode

Now, let's dive into how it all works, shall we?

Multiple texts

Let's take our example to the next level and add support for multiple texts. To do this, we need to create a new function that simulates the Backspace key and erases text.

We'll call this function erase(). It will work the opposite way to the type() function. It will remove the last character from the text element and decrease the index by 1 until all characters are removed. The cursor__blink class will be added or removed from the cursor element accordingly.

const erase = () => {
    if (charIndex > 0) {
        cursorEle.classList.remove('cursor__blink');
        textEle.textContent = textEle.textContent.slice(0, charIndex - 1);
        charIndex--;
        setTimeout(erase, 100);
    } else {
        cursorEle.classList.add('cursor__blink');
    }
};
Enter fullscreen mode Exit fullscreen mode

To keep track of the current text we're working on, we'll need a new index called arrayIndex to access the list of texts. The type() function will be updated so that we're working with the current text. When the current text is fully displayed, we'll start erasing it after 500 milliseconds.

const texts = [
    'Learning never exhausts the mind',
    'You teach best what you most need to learn',
    'In youth we learn; in age we understand',
];
let arrayIndex = 0;

const type = () => {
    if (charIndex < texts[arrayIndex].length) {
        cursorEle.classList.remove('cursor__blink');
        textEle.textContent += texts[arrayIndex].charAt(charIndex);
        charIndex++;
        setTimeout(type, 100);
    } else {
        cursorEle.classList.add('cursor__blink');
        setTimeout(erase, 500);
    }
};
Enter fullscreen mode Exit fullscreen mode

In the same way, once the current text has been wiped clean, we'll wait 500 milliseconds before typing the next one. Here's an example of what the erase function might look like:

const erase = () => {
    if (charIndex > 0) {
        cursorEle.classList.remove('cursor__blink');
        textEle.textContent = textEle.textContent.slice(0, charIndex - 1);
        charIndex--;
        setTimeout(erase, 100);
    } else {
        cursorEle.classList.add('cursor__blink');
        arrayIndex++;
        if (arrayIndex > texts.length - 1) {
            arrayIndex = 0;
        }
        setTimeout(type, 500);
    }
};
Enter fullscreen mode Exit fullscreen mode

Now, it's time to see the final demonstration!

See also


It's highly recommended that you visit the original post to play with the interactive demos.

If you found this series helpful, please consider giving the repository a star on GitHub or sharing the post on your favorite social networks 😍. Your support would mean a lot to me!

If you want more helpful content like this, feel free to follow me:

Top comments (0)