Typewriter Animation That Handles Something You Throw at It

No Comments

I watched Kevin Powell’s video the place he was capable of recreate a pleasant typewriter-like animation utilizing CSS. It’s neat and you need to undoubtedly test it out as a result of there are bonafide CSS methods in there. I’m positive you’ve seen different CSS makes an attempt at this, together with this web site’s very personal snippet.

Like Kevin, I made a decision to recreate the animation, however open it as much as JavaScript. That approach, we now have a couple of additional instruments that may make the typing really feel somewhat extra pure and much more dynamic. Most of the CSS options depend on magic numbers primarily based on the size of the textual content, however with JavaScript, we will make one thing that’s able to taking any textual content we throw at it.

So, let’s do this. On this tutorial, I’m going to indicate that we will animate a number of phrases simply by altering the precise textual content. No want to change the code each time you add a brand new phrase as a result of JavaScript will do this for you!

Beginning with the textual content

Let’s begin with textual content. We’re utilizing a monospace font to realize the impact. Why? As a result of every character or letter occupies an equal quantity of horizontal house in a monospaced font, which can come helpful once we’ll use the idea of steps() whereas animating the textual content. Issues are rather more predictable once we already know the precise width of a personality and all characters share the identical width.

We now have three components positioned inside a container: one aspect for the precise textual content, one for hiding the textual content, and one for animating the cursor.

<div class=”container”>
<div class=”text_hide”></div>
<div class=”textual content”>Typing Animation</div>
<div class=”text_cursor”></div>
</div>

We might use ::earlier than and ::after pseudo-elements right here, however they aren’t nice for JavaScript. Pseudo-elements are usually not a part of the DOM, however as an alternative are used as additional hooks for styling a component in CSS. It’d be higher to work with actual components.

We’re utterly hiding the textual content behind the .text_hide aspect. That’s key. It’s an empty div that stretches the width of the textual content and blocks it out till the animation begins—that’s once we begin to see the textual content transfer out from behind the aspect.

With the intention to cowl the whole textual content aspect, place the .text_hide aspect on prime of the textual content aspect having the identical top and width as that of the textual content aspect. Keep in mind to set the background-color of the .text_hide aspect precisely identical as that of the background surrounding the textual content so every little thing blends in collectively.

.container {
place: relative;
}
.textual content {
font-family: ‘Roboto Mono’, monospace;
font-size: 2rem;
}
.text_hide {
place: absolute;
prime: 0;
backside: 0;
left: 0;
proper: 0;
background-color: white;
}

The cursor

Subsequent, let’s make that little cursor factor that blinks because the textual content is being typed. We’ll maintain off on the blinking half for only a second and focus simply on the cursor itself.

Let’s make one other aspect with class .text_cursor. The properties are going to be much like the .text_hide aspect with a minor distinction: as an alternative of setting a background-color, we are going to maintain the background-color clear (since its technically pointless, then add a border to the left fringe of the brand new .text_cursor aspect.

.text_cursor{
place: absolute;
prime: 0;
backside: 0;
left: 0;
proper: 0;
background-color: clear;
border-left: 3px strong black;
}

Now we get one thing that appears like a cursor that’s prepared to maneuver because the textual content strikes:

JavaScript animation

Now comes the tremendous enjoyable half—let’s animate these things with JavaScript! We’ll begin by wrapping every little thing inside a operate known as typing_animation().

operate typing_animation(){
// code right here
}
typing_animation();

Subsequent activity is to retailer each character of textual content in a single array utilizing the break up() technique. This divides the string right into a substring that has just one character and an array containing all of the substrings is returned.

operate typing_animation(){
let text_element = doc.querySelector(“.textual content”);
let text_array = text_element.innerHTML.break up(“”);
}

For instance, if we take “Typing Animation” as a string, then the output is:

We are able to additionally decide the whole variety of characters within the string. With the intention to get simply the phrases within the string, we substitute break up(“”) with break up(” “). Observe that there’s a distinction between the 2. Right here, ” ” acts as a separator. Each time we encounter a single house, it would terminate the substring and retailer it as an array aspect. Then the method goes on for the whole string.

operate typing_animation(){
let text_element = doc.querySelector(“.textual content”);
let text_array = text_element.innerHTML.break up(“”);
let all_words = text_element.innerHTML.break up(” “);
}

For instance, for a string ‘Typing Animation’, the output will probably be,

Now, let’s calculate the size of the whole string in addition to the size of each particular person phrase.

operate typing_animation() {
let text_element = doc.querySelector(“.textual content”);
let text_array = text_element.innerHTML.break up(“”);
let all_words = text_element.innerHTML.break up(” “);
let text_len = text_array.size;

const word_len = all_words.map((phrase) => {
return phrase.size;
});
}

To get the size of the whole string, we now have to entry the size of the array containing all of the characters as particular person components. If we’re speaking in regards to the size of a single phrase, then we will use the map() technique, which accesses one phrase at a time from the all_words array after which shops the size of the phrase into a brand new array known as word_len. Each the arrays have the identical variety of components, however one incorporates the precise phrase as a component, and the opposite has the size of the phrase as a component.

Now we will animate! We’re utilizing the Net Animation API as a result of we’re going with pure JavaScript right here—no CSS animations for us on this instance.

First, let’s animate the cursor. It must blink on and off infinitely. We want keyframes and animation properties, each of which will probably be saved in their very own JavaScript object. Listed here are the keyframes:

doc.querySelector(“.text_cursor”).animate([
{
opacity: 0
},
{
opacity: 0, offset: 0.7
},
{
opacity: 1
}
], cursor_timings);

We now have outlined three keyframes as objects that are saved in an array. The time period offset: 0.7 merely implies that after 70% completion of the animation, the opacity will transition from 0 to 1.

Now, we now have to outline the animation properties. For that, let’s create a JavaScript object that holds them collectively:

let cursor_timings = {
period: 700, // milliseconds (0.7 seconds)
iterations: Infinity, // variety of instances the animation will work
easing: ‘cubic-bezier(0,.26,.44,.93)’ // timing-function
}

We may give the animation a reputation, identical to this:

let animation = doc.querySelector(“.text_cursor”).animate([
// keyframes
], //properties);

Right here’s a demo of what we now have completed up to now:

CodePen Embed Fallback

Nice! Now, let’s animate the .text_hide aspect that, true to its title, hides the textual content. We outline animation properties for this aspect:

let timings = {
easing: `steps(${Quantity(word_len[0])}, finish)`,
delay: 2000, // milliseconds
period: 2000, // milliseconds
fill: ‘forwards’
}

The easing property defines how the speed of animation will change over time. Right here, we now have used the steps() timing operate. This animates the aspect in discrete segments slightly than a easy steady animation—you realize, for a extra pure typing motion. For instance, the period of the animation is 2 seconds, so the steps() operate animates the aspect in 9 steps (one step for every character in “Animation”) for 2 seconds, the place every step has a period of two/9 = 0.22 seconds.

The top argument makes the aspect keep in its preliminary state till the period of first step is full. This argument is optionally available and its default worth is ready to finish. In order for you an in-depth perception on steps(), then you’ll be able to refer this superior article by Joni Trythall.

The fill property is similar as animation-fill-mode property in CSS. By setting its worth to forwards, the aspect will keep on the identical place as outlined by the final keyframe after the animation will get accomplished.

Subsequent, we are going to outline the keyframes.

let reveal_animation_1 = doc.querySelector(“.text_hide”).animate([
{ left: ‘0%’ },
{ left: `${(100 / text_len) * (word_len[0])}%` }
], timings);

Proper now we’re animating only one phrase. Later, we are going to see the way to animate a number of phrases.

The final keyframe is essential. Let’s say we need to animate the phrase “Animation.” Its size is 9 (as there are 9 characters) and we all know that it’s getting saved as a variable because of our typing_animation() operate. The declaration 100/text_len outcomes to 100/9, or 11.11%, which is the width of each character within the phrase “Animation.” Which means the width of each character is 11.11% the width of the whole phrase. If we multiply this worth by the size of the primary phrase (which in our case is 9), then we get 100%. Sure, we might have straight written 100% as an alternative of doing all these things. However this logic will assist us once we are animating a number of phrases.

The results of all of that is that the .text_hide aspect animates from left: 0% to left: 100%. In different phrases, the width of this aspect decreases from 100% to 0% because it strikes alongside.

We now have so as to add the identical animation to the .text_cursor aspect as properly as a result of we would like it to transition from left to proper together with the .text_hide aspect.

CodePen Embed Fallback

Yayy! We animated a single phrase. What if we need to animate a number of phrases? Let’s do this subsequent.

Animating a number of phrases

Let’s say we now have two phrases we would like typed out, maybe “Typing Animation.” We animate the primary phrase by following the identical process we did final time. This time, nevertheless, we’re altering the easing operate worth within the animation properties.

let timings = {
easing: `steps(${Quantity(word_len[0] + 1)}, finish)`,
delay: 2000,
period: 2000,
fill: ‘forwards’
}

We now have elevated the quantity by one step. Why? Nicely, what a few single house after a phrase? We should take that into consideration. However, what if there is just one phrase in a sentence? For that, we are going to write an if situation the place, if the variety of phrases is the same as 1, then steps(${Quantity(word_len[0])}, finish). If the variety of phrases just isn’t equal to 1, then steps(${Quantity(word_len[0] + 1)}, finish).

operate typing_animation() {
let text_element = doc.querySelector(“.textual content”);
let text_array = text_element.innerHTML.break up(“”);
let all_words = text_element.innerHTML.break up(” “);
let text_len = text_array.size;
const word_len = all_words.map((phrase) => {
return phrase.size;
})
let timings = {
easing: `steps(${Quantity(word_len[0])}, finish)`,
delay: 2000,
period: 2000,
fill: ‘forwards’
}
let cursor_timings = {
period: 700,
iterations: Infinity,
easing: ‘cubic-bezier(0,.26,.44,.93)’
}
doc.querySelector(“.text_cursor”).animate([
{
opacity: 0
},
{
opacity: 0, offset: 0.7
},
{
opacity: 1
}
], cursor_timings);
if (all_words.size == 1) {
timings.easing = `steps(${Quantity(word_len[0])}, finish)`;
let reveal_animation_1 = doc.querySelector(“.text_hide”).animate([
{ left: ‘0%’ },
{ left: `${(100 / text_len) * (word_len[0])}%` }
], timings);
doc.querySelector(“.text_cursor”).animate([
{ left: ‘0%’ },
{ left: `${(100 / text_len) * (word_len[0])}%` }
], timings);
} else {
doc.querySelector(“.text_hide”).animate([
{ left: ‘0%’ },
{ left: `${(100 / text_len) * (word_len[0] + 1)}%` }
], timings);
doc.querySelector(“.text_cursor”).animate([
{ left: ‘0%’ },
{ left: `${(100 / text_len) * (word_len[0] + 1)}%` }
], timings);
}
}
typing_animation();

For a couple of phrase, we use a for loop to iterate and animate each phrase that follows the primary phrase.

for(let i = 1; i < all_words.size; i++){
// code
}

Why did we take i = 1? As a result of by the point this for loop is executed, the primary phrase has already been animated.

Subsequent, we are going to entry the size of the respective phrase:

for(let i = 1; i < all_words.size; i++){
const single_word_len = word_len[i];
}

Let’s additionally outline the animation properties for all phrases that come after the primary one.

// the next code goes contained in the for loop
let timings_2 = {
easing: `steps(${Quantity(single_word_len + 1)}, finish)`,
delay: (2 * (i + 1) + (2 * i)) * (1000),
period: 2000,
fill: ‘forwards’
}

An important factor right here is the delay property. As you realize, for the primary phrase, we merely had the delay property set to 2 seconds; however now we now have to extend the delay for the phrases following the primary phrase in a dynamic approach.

The primary phrase has a delay of two seconds. The period of its animation can be two seconds which, collectively, makes 4 complete seconds. However there must be some interval between animating the primary and the second phrase to make the animation extra real looking. What we will do is add a two-second delay between every phrase as an alternative of 1. That makes the second phrase’s total delay 2 + 2 + 2, or six seconds. Equally, the whole delay to animate the third phrase is 10 seconds, and so forth.

The operate for this sample goes one thing like this:

(2 * (i + 1) + (2 * i)) * (1000)

…the place we’re multiplying by 1000 to transform seconds to milliseconds.

Size of the phrasePeriod taken by one character to animate62/6 = 0.33 seconds82/8 = 0.25 seconds92/9 = 0.22 seconds122/12 = 0.17 seconds* Complete period is 2 seconds

The longer the phrase, the quicker it’s revealed. Why? As a result of the period stays the identical irrespective of how prolonged the phrase is. Mess around with the period and delay properties to get issues excellent.

Keep in mind once we modified the steps() worth by considering a single house after a phrase? In the identical approach, the final phrase within the sentence doesn’t have an area after it, and thus, we should always take that into consideration in one other if assertion.

// the next code goes contained in the for loop
if (i == (all_words.size – 1)) {
timings_2.easing = `steps(${Quantity(single_word_len)}, finish)`;
let reveal_animation_2 = doc.querySelector(“.text_hide”).animate([
{ left: `${left_instance}%` },
{ left: `${left_instance + ((100 / text_len) * (word_len[i]))}%` }
], timings_2);
doc.querySelector(“.text_cursor”).animate([
{ left: `${left_instance}%` },
{ left: `${left_instance + ((100 / text_len) * (word_len[i]))}%` }
], timings_2);
} else {
doc.querySelector(“.text_hide”).animate([
{ left: `${left_instance}%` },
{ left: `${left_instance + ((100 / text_len) * (word_len[i] + 1))}%` }
], timings_2);
doc.querySelector(“.text_cursor”).animate([
{ left: `${left_instance}%` },
{ left: `${left_instance + ((100 / text_len) * (word_len[i] + 1))}%` }
], timings_2);
}

What’s that left_instance variable? We haven’t mentioned it, but it’s the most important a part of what we’re doing. Let me clarify it.

0% is the preliminary worth of the primary phrase’s left property. However, the second phrase’s preliminary worth ought to equal the primary phrase’s remaining left property worth.

if (i == 1) {
var left_instance = (100 / text_len) * (word_len[i – 1] + 1);
}

word_len[i – 1] + 1 refers back to the size of the earlier phrase (together with a white house).

We now have two phrases, “Typing Animation.” That makes text_len equal 16 which means that every character is 6.25% of the total width (100/text_len = 100/16) which is multiplied by the size of the primary phrase, 7. All that math offers us 43.75 which is, actually, the width of the primary phrase. In different phrases, the width of the primary phrase is 43.75% the width of the whole string. Which means that the second phrase begins animating from the place the primary phrase left off.

Final, let’s replace the left_instance variable on the finish of the for loop:

left_instance = left_instance + ((100 / text_len) * (word_len[i] + 1));

CodePen Embed Fallback

Now you can enter as many phrases as you need in HTML and the animation simply works!

Bonus

Have you ever seen that the animation solely runs as soon as? What if we need to loop it infinitely? It’s doable:

CodePen Embed Fallback

There we go: a extra strong JavaScript model of a typewriting animation. It’s tremendous cool that CSS additionally has an strategy (and even a number of approaches) to do the identical type of factor. CSS may even be the higher strategy in a given scenario. However once we want enhancements that push past what CSS can deal with, sprinkling in some JavaScript does the trick fairly properly. On this case, we added assist for all phrases, no matter what number of characters they comprise, and the flexibility to animate a number of phrases. And, with a small additional delay between phrases, we get an excellent natural-looking animation.

That’s it, hope you discovered this attention-grabbing! Signing off.

The publish Typewriter Animation That Handles Something You Throw at It appeared first on CSS-Tips. You may assist CSS-Tips by being an MVP Supporter.

    About Marketing Solution Australia

    We are a digital marketing company with a focus on helping our customers achieve great results across several key areas.

    Request a free quote

    We offer professional SEO services that help websites increase their organic search score drastically in order to compete for the highest rankings even when it comes to highly competitive keywords.

    Subscribe to our newsletter!

    More from our blog

    See all posts

    Leave a Comment