Lesson 14 - Timers and animations in JavaScript

JavaScript Basics Timers and animations in JavaScript

Welcome to the next lesson of our JavaScript online course. The previous lesson, 2D canvas context in JavaScript, was about the 2D context of a canvas. In today's JavaScript tutorial, we're going to look at timing and then continue with animations.

Timers

We can handle timers in JavaScript using two functions.

setInterval() and setTimeout()

The setInterval() and setTimeout() functions accept two parameters. The function that will be called and the time interval (how often or after what time it'll be called). We specify the intervals in milliseconds (1 second = 1000 milliseconds). The difference between these two methods is quite fundamental. While setInterval() will call the method every X milliseconds, setTimeout() will call it only once when X milliseconds elapses.

Example

The goal will be to create a typing text. At first, the letter "H" will show. One second later, "e" will show up. This will continue with letters "l", "l", "o", "w", "o", "r", "l", "d". Once all the letters are typed, the text is cleared and it all starts again.

Now we won't have any element in the HTML code anymore, we'll create it via JavaScript (the implementation is only up to us, but we'll leave the <body> empty to practice the DOM manipulation :) ).

We'll store the text to be typed into a text variable and create a <p> element. Since it can happen that even the <body> wouldn't be loaded yet (if we imported the script in the <head>), we'll insert the <p> element into the <body> element not before the page is loaded.

let text = "Hello world";
let element = document.createElement("p");
window.onload = function () {
        document.body.appendChild(element);
}

Now we'll proceed to the function which will be changing the text in our element:

function changeText() {

}

At first in this function, we'll verify that we haven't already written all the letters. If we asked for the next letter when we're at the end and there're no more letters left, we'd cause an error. If the text variable already equals to the element's content, we'll change the element's value to an empty string so that the text can be typed again. We'll also add the else branch where we'll insert further code.

if (element.textContent == text) {
        element.textContent = "";
} else {
}

Next, we'll retrieve the letter (character) which we'll add to the element's text. We already know that length contains a string's length. So we'll get the next character to be typed by measuring the length of the string that is already typed on the screen and adding 1 to it. There's a catch. While the string length is counted from 1 (1 character = length 1), the character at a certain position (index) is counted from zero (1st character = index 0), so we have to subtract 1. Finally +1 (for the next character) and -1 (because 0 is the first character) eliminates, so we take the character at the index of the length of the text already typed on the screen:

let letterToType = text[element.textContent.length];

And we add the letter to the text of the element.

element.textContent += letterToType;

All we have to do is to set the interval now. Let's call the setInterval() method in the onload event handler. We'll pass changeText as the name of the function (without parentheses) as the first parameter, and the number 1000 to make the text change every 1 second as the second parameter.

setInterval(changeText, 1000);

The interval will start one second later. In the meantime, the application will look like it doesn't respond for a second. That's why we'll call the changeText() method manually above it:

changeText();
setInterval(changeText, 1000);

You can still improve the application since it'll appear stuck when typing whitespaces.

Typing text
localhost

Advanced animations

It'd be nice to have more complex animations on our website. The simple ones can be done in CSS, but the more complicated ones have to be done in JavaScript. The core principle of animations is that we affect properties of the animated object in some interval.

Let's take autumn web decorations as an example. We'll program a script that animates falling leaves, they'll fall from top to bottom. We'll place the images of the leaves in the root <body> element and each image will have a data-autumn data-attribute. We'll select only these images, because there might be (and will be) other images on the website and we won't want to animate those.

Download the leaf image below and put it into a new project with the following HTML contents:

Leaf

<img src="leaf.jpg" data-autumn alt="leaf" />
<img src="leaf.jpg" data-autumn alt="leaf" />
<img src="leaf.jpg" data-autumn alt="leaf" />
<img src="leaf.jpg" data-autumn alt="leaf" />
<img src="leaf.jpg" data-autumn alt="leaf" />

Let's attach a CSS style to the project in which we'll set the absolute position to the images (again, we'll affect only the images with the data-autumn attribute). We'll also style the <body> so it won't display scrollbars.

body > img[data-autumn] {
        position:absolute;
}

body {
        overflow:hidden;
}

Let's define a leaves variable to which we'll assign the leaves images with the data-autumn attribute once the page is loaded.

let leaves;
window.onload = function () {
        leaves = document.querySelectorAll("body > img[data-autumn]");
}

We'll iterate through the leaves using a loop:

for (let i = 0; i < leaves.length; i++)

We want to set the default position to the leaves. This will be one-fifth of the window width for each leaf from the left (to justify them, as can be seen further). And minus their height from the top (so they'd fall from behind the top window edge).

Window size

Sometimes (like now) we need to work with window size. There're several ways and properties out there that are related to this size.

The actual screen size

We can retrieve the real screen size using the width and height properties on the screen object.

Available size

This is the size that applications can use. In other words, essentially the screen size above minus the size of the system panels (such as the taskbar). We can find the properties on the screen object again, they're availWidth and availHeight.

The website window size

Finally, we're getting to the most important size for us. It's the size of the area our applications can use. We can find the properties on the window object as innerWidth and innerHeight.

Setting CSS properties

For the time being, we still lack one essential piece of information which is how to set CSS properties of DOM elements.

All DOM elements have the style property which contains properties named as CSS properties. They're not written using the dash notation, but camelCase (the first word is all in lowercase letters, each new word has the first letter upper-cased, there're no spaces). So for example:

document.body.style.backgroundColor = "red";

sets the color of the <body> element to red.

Let's go back to our falling leaves. We'll set the positions. Remember to not forget to specify the units when setting CSS values:

leaves[i].style.left = i * window.innerWidth / leaves.length + "px";
leaves[i].style.top = -leaves[i].height + "px";

Moving the leaves

Now let's implement a move() function which will move the leaves down. The function will iterate through all the leaves using a loop and set the new positions to them. The new position will be based on the actual one (which we must parse to remove the CSS unit from the value), and then we'll add some reasonable value to it so the animation will be neither too fast nor too slow. You can try to edit the value (the value 2 in the code).

function move() {
        for (let i = 0; i < leaves.length; i++) {
                let newPosition = parseInt(leaves[i].style.top) + 2;
                leaves[i].style.top = newPosition + "px";
        }
}

We still have to deal with the case when a leaf leaves the window. In this case, we need to reset its position to minus the height of the image. Let's place a condition between the previous two lines so we don't have to set the CSS value twice.

if (newPosition > window.innerHeight) {
        newPosition = -leaves[i].height;
}

Finally, in the window's load event handler, we'll set the interval to a reasonable value (you can try to adjust it yourself again).

setInterval(move, 20);

Because the process was more complex, let's show the complete script for review:

let leaves;

function move() {
        for (let i = 0; i < leaves.length; i++) {
                leaves[i].style.left = i * window.innerWidth / leaves.length + "px";
                let newPosition = parseInt(leaves[i].style.top) + 2;
                if (newPosition > window.innerHeight) {
                        newPosition = -leaves[i].height;
                }
                leaves[i].style.top = newPosition + "px";
        }
}

window.onload = function () {
        leaves = document.querySelectorAll("body > img[data-autumn]");
        setInterval(move, 20);
}

Run the application. You'll see the leaves falling from top to bottom, and they'll reset once they leave the screen.

Falling leaves
localhost

Congratulations, you've finished your first JavaScript animation. You can improve it, of course, to make it even more interesting.

Canvas animations

We can create animations using <canvas> in a similar way. In some interval, we'll clear the entire canvas, draw everything and so over and over again. We can program a wheel of fortune as an example. We'll load it from a static image to keep things simple. So, let's create a page with a <canvas> in it and select it in JavaScript. We'll add an image with id="wheel" to the page.

Download the image below and insert it along with the following HTML code to a new project:

Wheel of fortune

<img src="wheel.png" id="wheel" />
<canvas id="canvas" width="500" height="500"></canvas>

In the script, we'll select these objects and also add an angle variable where the current angle of the rotation will be stored. We won't forget to hide the image from the page.

let canvas;
let context;
let angle = 0;
let image;

window.onload = function () {
        canvas = document.getElementById("canvas");
        context = canvas.getContext("2d");
        image = document.getElementById("wheel");
        image.style.display = "none";
}

In the redraw() method, we simply clear the canvas and draw the wheel again. First, we'll move and rotate the context appropriately and then we'll draw the image. Finally, we'll add one degree to the angle which corresponds with Math.PI / 180 radians since Math.PI is one half of the circle and one half of the circle has 180 degrees.

function redraw() {
        context.clearRect(0, 0, 500, 500);
        context.save();
        context.translate(250, 250);
        context.rotate(angle);
        context.drawImage(image, -225, -225);
        context.restore();
        angle += Math.PI / 180; // Rotates by 1 degree
}

In the page's load event, we'll set the interval as usual. We'll also draw the wheel right at the start so we won't have to wait for the first interval to come.

setInterval(redraw, 20);
redraw();

The result:

Wheel of fortune
localhost

Congratulations, you should be now able to handle animations. You can challenge your skills using our exercises.

Doing animations right

All of our animation solutions work, but let's look at them in terms of performance. When the user leaves our browser tab, the animation will keep running and drain the CPU. It's even worse on mobile devices, where we can notice a decrease in performance. We'll learn how to optimize our animations for the web browser in the next lesson, JS requestAnimationFrame - For better drawing.


 

Download

Downloaded 1x (218.61 kB)
Application includes source codes in language JavaScript

 

 

Article has been written for you by Michal Zurek
Avatar
Do you like this article?
1 votes
Thumbnail
All articles in this section
JavaScript basic constructs
Activities (5)

 

 

Comments

To maintain the quality of discussion, we only allow registered members to comment. Sign in. If you're new, Sign up, it's free.

No one has commented yet - be the first!