How to Create an Animated Favicon Loader with JavaScript

Favicons are a crucial part of online branding, they give a visual cue to users, and help them distinguish your site from others. Although most favicons are static, it is possible to create animated favicons as well.

A constantly moving favicon is certainly annoying for most users, and also harms accessibility, however when it’s only animated for a short time in response to a user action or a background event, such as a page load, it can provide extra visual information—therefore improving user experience.

Let’s Talk About the Importance of Favicons

Let’s Talk About the Importance of Favicons

Discover the importance of favicons for your website and learn how to create and implement them. Improve your... Read more

In this post, I’ll show you how to create an animated circular loader in a HTML canvas, and how you can use it as a favicon. An animated favicon loader is a great tool to visualize the progress of any action performed on a page, such as file uploading or image processing. You can have a look at the demo belonging to this tutorial on Github as well.

Canvas Loader Gif Demo

1. Create the <canvas> element

First, we need to create a canvas animation that draws a full circle, 100 percent in total (this will be important when we need to increment the arc).

<button id=lbtn>Load</button>
<canvas id=cvl width=16 height=16></canvas>

I’m using the standard favicon size, 16*16 pixels, for the canvas. You can use a size bigger than that if you want, but note that the canvas image will be scaled down to the 162 pixel area when it’s applied as a favicon.

2. Check if <canvas> is supported

Inside the onload() event handler, we get a reference for the canvas element [cv] using the querySelector() method, and refer its 2D drawing context object [ctx] with the help of the getContext() method.

onload = function() {
    cv = document.querySelector('#cvl'),
    ctx = cv.getContext('2d');

    if (!!ctx) {
        /* ... */
    }
};

We also need to check if the canvas is supported by the UA by making sure that the drawing context object [ctx] exists and isn’t undefined. We’ll place all the code belonging to the load event into this if condition.

Master DOM Manipulation with 15 Essential JavaScript Methods

Master DOM Manipulation with 15 Essential JavaScript Methods

As a web developer, you frequently need to manipulate the DOM, the object model that is used by... Read more

3. Create the initial variables

Let’s create three more global variables, s for the starting angle of the arc, tc for the id for the setInterval() timer, and pct for the percentage value of the same timer. The code tc = pct = 0 assigns 0 as the initial value for the tc and pct variables.

onload = function() {
    cv = document.querySelector('#cvl'),
    ctx = cv.getContext('2d');

    if (!!ctx) {
        s = 1.5 * Math.PI,
        tc = pct = 0;
    }
};

To show how the value of s was calculated, let me quickly explain how arc angles work.

Arc angles

The subtended angle (the angle composed of the two rays that define an arc) of the circumference of a circle is 2π rad, where rad is the radian unit symbol. This makes the angle for a quarter arc equal to 0.5π rad.

subtended angle of circumference

When visualizing the loading progress, we want the circle on the canvas to be drawn from the top position rather than the default right.

Going clockwise (default direction arc is drawn on the canvas) from the right position, the top point is reached after three quarters, i.e. at an angle of 1.5π rad. Hence, I’ve created the variable s = 1.5 * Math.PI to later denote the starting angle for the arcs to be drawn from on the canvas.

4. Style the circle

For the drawing context object, we define the lineWidth and strokeStyle properties of the circle we are going to draw in the next step. The strokeStyle property stands for its color.

onload = function() {
    cv = document.querySelector('#cvl'),
    ctx = cv.getContext('2d');

    if (!!ctx) {
        s = 1.5 * Math.PI,
        tc = pct = 0;

        ctx.lineWidth = 2;
        ctx.strokeStyle = 'fuchsia';
    }
};

5. Draw the circle

We add a click event handler to the Load button [#lbtn] which triggers a setInterval timer of 60 milliseconds, that executes the function responsible for drawing the circle [updateLoader()] every 60ms till the circle is fully drawn.

The setInterval() method returns a timer id to identify its timer which is assigned to the tc variable.

onload = function() {
    cv = document.querySelector('#cvl'),
    ctx = cv.getContext('2d');

    if (!!ctx) {
        s = 1.5 * Math.PI,
        tc = pct = 0,
        btn = document.querySelector('#lbtn');

        ctx.lineWidth = 2;
        ctx.strokeStyle = 'fuchsia';

        btn.addEventListener('click', function() {
            tc = setInterval(updateLoader, 60);
        });
    }
};

6. Create the updateLoader() custom function

It’s time to create the custom updateLoader() function that is to be called by the setInterval() method when the button is clicked (the event is triggered). Let me show you the code first, then we can go along with the explanation.

function updateLoader() {
    ctx.clearRect(0, 0, 16, 16);
    ctx.beginPath();
    ctx.arc(8, 8, 6, s, (pct * 2 * Math.PI / 100 + s));
    ctx.stroke();

    if (pct === 100) {
        clearInterval(tc);
        return;
    }

    pct++;
}

The clearRect() method clears the rectangular area of the canvas defined by its parameters: the (x, y) coordinates of the top-left corner. The clearRect(0, 0, 16, 16) line erases everything in the 16*16 pixels canvas we have created.

The beginPath() method creates a new path for the drawing, and the stroke() method paints on that newly created path.

At the end of the updateLoader() function, the percentage count [pct] is incremented by 1, and prior to the increment we check if it equals to 100. When it’s 100 percent, the setInterval() timer (identified by the timer id, tc) is cleared with the help of the clearInterval() method.

The first three parameters of the arc() method are the (x, y) coordinates of center of the arc and its radius. The fourth and fifth parameters represent the start and end angles at which the drawing of the arc begins and ends.

We already decided the starting point of the loader circle, which is at the angle s, and it’ll be the same in all the iterations.

The end angle however will increment with the percent count, we can calculate the size of the increment in the following way. Say 1% (the value 1 out of 100) is equivalent to angle α out of 2π in a circle (2π = angle of the whole circumference), then the same can be written as the following equation:

1/100 = α/2π

On rearranging the equation:

α = 1 * 2π /100
α = 2π/100

So, 1% is equivalent to the angle 2π/100 in a circle. Thus, the end angle during each percent increment is computed by multiplying 2π/100 by the percentage value. Then the result is added to s (start angle), so the arcs are drawn from the same starting position every time. This is why we used the pct * 2 * Math.PI / 100 + s formula to calculate the end angle in the code snippet above.

7. Add the favicon

Let’s place a favicon link element into the HTML <head> section, either directly or via JavaScript.

<link rel="icon" type="image/ico" >

In the updateLoader() function, first we fetch the favicon using the querySelector() method, and assign it to the lnk variable. Then we need to export the canvas image every time an arc is drawn into an encoded image by using the toDataURL() method, and assign that data URI content as the favicon image. This creates an animated favicon which is the same as the canvas loader.

onload = function() {
    cv = document.querySelector('#cvl'),
    ctx = cv.getContext('2d');

    if (!!ctx) {
        s = 1.5 * Math.PI,
        tc = pct = 0,
        btn = document.querySelector('#lbtn'),
        lnk = document.querySelector('link[rel="icon"]');

        ctx.lineWidth = 2;
        ctx.strokeStyle = 'fuchsia';

        btn.addEventListener('click', function() {
            tc = setInterval(updateLoader, 60);
        });
    }
};

function updateLoader() {
    ctx.clearRect(0, 0, 16, 16);
    ctx.beginPath();
    ctx.arc(8, 8, 6, s, (pct * 2 * Math.PI / 100 + s));
    ctx.stroke();

    lnk.href= cv.toDataURL('image/png');

    if (pct === 100) {
        clearTimeout(tc);
        return;
    }

    pct++;
}

You can have a look at the full code on Github.

Bonus: Use the loader for async events

When you need to use this canvas animation in conjunction with a loading action in a web page, assign the updateLoader() function as the event handler for the progress() event of the action.

For instance, our JavaScript will change like this in AJAX:

onload = function() {
    cv = document.querySelector('#cvl'),
    ctx = cv.getContext('2d');

    if (!!ctx) {
        s = 1.5 * Math.PI,
        lnk = document.querySelector('link[rel="icon"]');

        ctx.lineWidth = 2;
        ctx.strokeStyle = 'fuchsia';
    }

    var xhr = new XMLHttpRequest();
    xhr.addEventListener('progress', updateLoader);
    xhr.open('GET', 'https://xyz.com/abc');
    xhr.send();
};

function updateLoader(evt) {
    ctx.clearRect(0, 0, 16, 16);
    ctx.beginPath();
    ctx.arc(8, 8, 6, s, (evt.loaded*2*Math.PI/evt.total+s));
    ctx.stroke();

    lnk.href = cv.toDataURL('image/png');
}

In the arc() method, replace the percentage value [pct] with the loaded property of the event—it denotes how much of the file has been loaded, and in place of 100 use the total property of the ProgressEvent, which denotes the total amount to be loaded.

There’s no need for setInterval() in such cases, as the progress() event is automatically fired as the loading progresses.

WebsiteFacebookTwitterInstagramPinterestLinkedInGoogle+YoutubeRedditDribbbleBehanceGithubCodePenWhatsappEmail