It’s the first thing your eyes look for when you’re switching tabs.
That’s one way of explaining what a favicon is. The tab area is a much more precious screen real-estate than what most assume. If done right, besides being a label with icon, it can be the perfect billboard to represent what’s in or what’s happening on a web page.

Favicons are actually at their most useful when you’re not active on a tab. Here’s an example:
Imagine you’re backing up photos from your recent summer vacation to a cloud service. While they are uploading, you’ve opened a new tab to gather details about the places you went on vacation to later annotate those photos. One thing led to the other, and now you’re watching Casey Neistat on the seventh tab. But you can’t continue your YouTube marathon without the anxious intervals of checking back on the cloud service page to see if the photos have been uploaded.
It’s this type of situation where we can get creative! What if we could dynamically change the pixels in that favicon and display the upload progress? That’s exactly what we’ll do in this article.
In supported browsers, we can display a loading/progress animation as a favicon with the help of JavaScript, HTML <canvas>
and some centuries-old geometry.
Jumping straight in, we’ll start with the easiest part: adding the icon and canvas elements to the HTML.
<head>
<link rel="icon" type="image/png" href="" width=32px>
</head>
<body>
<canvas width=32 height=32></canvas>
</body>
In practical use, you would want to hide the <canvas>
on the page, and one way of doing that is with the HTML hidden
attribute.
<canvas hidden width=32 height=32></canvas>
I’m going to leave the <canvas>
visible on the page for you to see both the favicon and canvas images animate together.
Both the favicon and the canvas are given a standard favicon size: 32 square pixels.
For demo purposes, in order to trigger the loading animation, I’m adding a button to the page which will start the animation when clicked. This also goes in the HTML:
<button>Load</button>
Now let’s set up the JavaScript. First, a check for canvas support:
onload = ()=> {
canvas = document.querySelector('canvas'),
context = canvas.getContext('2d');
if (!!context) {
/* if canvas is supported */
}
};
Next, adding the button click event handler that will prompt the animation in the canvas.
button = document.querySelector('button');
button.addEventListener('click', function() {
/* A variable to track the drawing intervals */
n = 0,
/* Interval speed for the animation */
loadingInterval = setInterval(drawLoader, 60);
});
drawLoader
will be the function doing the drawing at intervals of 60 milliseconds each, but before we code it, I want to define the style of the lines of the square to be drawn. Let’s do a gradient.
/* Style of the lines of the square that'll be drawn */
let gradient = context.createLinearGradient(0, 0, 32, 32);
gradient.addColorStop(0, '#c7f0fe');
gradient.addColorStop(1, '#56d3c9');
context.strokeStyle = gradient;
context.lineWidth = 8;
In drawLoader
, we’ll draw the lines percent-wise: during the first 25 intervals, the top line will be incrementally drawn; in second quarter, the right line will be drawn; and so forth.
The animation effect is achieved by erasing the <canvas>
in each interval before redrawing the line(s) from previous interval a little longer.
During each interval, once the drawing is done in the canvas, it’s quickly translated to a PNG image to be assigned as the favicon.
function drawLoader() {
with(context) {
clearRect(0, 0, 32, 32);
beginPath();
/* Up to 25% */
if (n<=25){
/*
(0,0)-----(32,0)
*/
// code to draw the top line, incrementally
}
/* Between 25 to 50 percent */
else if(n>25 && n<=50){
/*
(0,0)-----(32,0)
|
|
(32,32)
*/
// code to draw the top and right lines.
}
/* Between 50 to 75 percent */
else if(n>50 && n<= 75){
/*
(0,0)-----(32,0)
|
|
(0,32)----(32,32)
*/
// code to draw the top, right and bottom lines.
}
/* Between 75 to 100 percent */
else if(n>75 && n<=100){
/*
(0,0)-----(32,0)
| |
| |
(0,32)----(32,32)
*/
// code to draw all four lines of the square.
}
stroke();
}
// Convert the Canvas drawing to PNG and assign it to the favicon
favicon.href = canvas.toDataURL('image/png');
/* When finished drawing */
if (n === 100) {
clearInterval(loadingInterval);
return;
}
// Increment the variable used to keep track of the drawing intervals
n++;
}
Now to the math and the code for drawing the lines.
Here’s how we incrementally draw the top line at each interval during the first 25 intervals:
n = current interval,
x = x-coordinate of the line’s end point at a given interval.
(y-coordinate of the end point is 0 and start point of the line is 0,0)
At the completion of all 25 intervals, the value of x is 32 (the size of the favicon and canvas.)
So…
x/n = 32/25
x = (32/25) * n
The code to apply this math and draw the line is:
moveTo(0, 0); lineTo((32/25)*n, 0);
For the next 25 intervals (right line), we target the y coordinate similarly.
moveTo(0, 0); lineTo(32, 0);
moveTo(32, 0); lineTo(32, (32/25)*(n-25));
And here’s the instruction to draw all four of the lines with the rest of the code.
function drawLoader() {
with(context) {
clearRect(0, 0, 32, 32);
beginPath();
/* Up to 25% of the time assigned to draw */
if (n<=25){
/*
(0,0)-----(32,0)
*/
moveTo(0, 0); lineTo((32/25)*n, 0);
}
/* Between 25 to 50 percent */
else if(n>25 && n<=50){
/*
(0,0)-----(32,0)
|
|
(32,32)
*/
moveTo(0, 0); lineTo(32, 0);
moveTo(32, 0); lineTo(32, (32/25)*(n-25));
}
/* Between 50 to 75 percent */
else if(n>50 && n<= 75){
/*
(0,0)-----(32,0)
|
|
(0,32)----(32,32)
*/
moveTo(0, 0); lineTo(32, 0);
moveTo(32, 0); lineTo(32, 32);
moveTo(32, 32); lineTo(-((32/25)*(n-75)), 32);
}
/* Between 75 to 100 percent */
else if(n>75 && n<=100){
/*
(0,0)-----(32,0)
| |
| |
(0,32)----(32,32)
*/
moveTo(0, 0); lineTo(32, 0);
moveTo(32, 0); lineTo(32, 32);
moveTo(32, 32); lineTo(0, 32);
moveTo(0, 32); lineTo(0, -((32/25)*(n-100)));
}
stroke();
}
// Convert the Canvas drawing to PNG and assign it to the favicon
favicon.href = canvas.toDataURL('image/png');
/* When finished drawing */
if (n === 100) {
clearInterval(loadingInterval);
return;
}
// Increment the variable used to keep track of drawing intervals
n++;
}
That’s all! You can see and download the demo code from this GitHub repo. Bonus: if you’re looking for a circular loader, check out this repo.
You can use any shape you want, and if you use the fill
attribute in the canvas drawing, that’ll give you a different effect.
Just curios, why the
if(!!context)
check instead of justif(context)
? Doesn’t the!!
just convert a truthy value totrue
, which is the exact same thing thatif(condition)
checks for? Is there some case I’m missing that this handles better?+1
Asked myself the same thing when i saw the use case!
It’s because
context
is not a boolean value, so you can’t just doif(context)
, it’s like doingif(3)
, which doesn’t make a lot of sense. (Even though i don’t know how js handles those cases right now)So to get a boolean value,
context
is first negated with a!
, which always returns a boolean. But because that boolean is the wrong way around, we negate it again with another!
.So basically if
context
wasnull
,!context
would returntrue
, so we do!!context
(you can look at it as!(!context)
) to get what we need, which isfalse
.And if it does exist,
!context
would be false, so again we do!!context
to gettrue
as the result.Scott, you’re right :) see https://developer.mozilla.org/en-US/docs/Glossary/Truthy
Favicon can also be used as a some kind of scrolling text messenger: https://kawalekkodu.pl/wiadomosc-z-czapy-czyli-o-nietypowym-wykorzystaniu-favicon
P.S. Remember that setInterval/setTimeout slows and stops because inactive tab.
The line assigning to the favicon seems not to work for me. Executing “favicon.href = …” gives console error “favicon is not defined”. Suggestions?
Konrad, examine the code in the GitHub repo linked near the bottom of the article. The script.js file shows the required declaration: favicon = document.querySelector(‘link[rel*=”icon”]’);
Preethi, perhaps editing the article to include this line of code might remove any confusion.
Observed in Chrome that, The animation is fast if the tab is active and slow if the tab is not active. A performance optimisation I guess.
This is not optimisation thing. This is what I’ve mentioned in my above comment. Check this out.
Really cool idea!
However, is there an alternative to
setInterval
as it slows down extremely on inactive tabs?Yes, please check my comment above. There is link to my blog, but in polish language (use Google translator), and there is solution also.
All the timing based APIs slow down when the tab is inactive. This is an intentional decision by browsers to help conserve battery life and other computer resources. In a real world scenario you would likely be using live progress data, so the animation might be jittery, but not “slow” just because the tab is inactive. An alternative if you care about just doing the animation rather than showing actual data, is to detect how much time has actually passed between each frame, and draw your canvas as a percentage of time rather than just incrementing by 1 each frame. The frame rate will still go down when the tab is innactive, but the animation will still be a consistent duration.
This is brilliant! The demo link and GitHub code make it really clear (the code in the article is is missing some key parts)
so far I’ve only seen twitter’s favicon change for notifications (even in my bookmarks bar). this is a lovely trick i’d love to use