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>
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;}
}
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()
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)
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
}
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;
}
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)
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()
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;
}
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
//...
}
End!
Latest comments (6)
Hello, Actually I want to ask you on how to make if we have a
<a>
tag, because your example can't render HTML tagsLooking very nice!
But I have question..
How do you stop de loop after it is finished?
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 😁
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.
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. :)
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!!