How to Create a Dynamic .favicon Procedurally

You probably know of the .favicon as the little icon in the left corner of your tabs. These icons are often overlooked, but what if I told you that you can play games or watch videos on them? When I learned that icons could be dynamic, I knew I had to give it a try. This tutorial is going to cover making a dynamic .favicon using pure JavaScript. To see the end result of what we’re going to make, look at the .favicon for this website! The icon slowly fades and changes color over time.

Here’s a short preview of what we’re going to get in to. First, we create a HTML5 canvas and use that to create a gradient image using some predefined colors. After that’s done, we create another image using the same template and some tweaked behavior. Next, we implement fading between the two images. At this point the icon is essentially done. For polish, we add feathered edges with customizable sharpness and size.

This tutorial is fairly long, so here’s a link to each of the sections:

Getting Started

The first thing you’re going to want to do is get a testing environment set up. I would definitely recommend using Google Chrome or Mozilla Firefox to build this, as they are the most modern and supported browsers right now. Next, if you already have a webpage or a local webserver set up, great! Just create a new blank html page and include a script in the body, like so:

<!doctype html>
<html>
<head>
</head>
<body>
<script>

// Code for this tutorial will go here

</script>
</body>
</html>


If you don’t have an environment already set up, I recommend using a fun website called JSFiddle to test your changes. There’s no HTML or page setup needed to test! The drawback is that you can’t directly modify the .favicon on that website. Don’t worry, we can simply display the canvas in addition to writing it to the icon.

First Steps

Let’s start out by displaying a basic canvas. The first thing we need to do is actually create the canvas element. Most people have a canvas element just hanging around in their HTML waiting to be used. Since we’re doing this procedurally, we might as well make the HTML procedural too!

We also need to answer an important question. How big is our canvas going to be? Favicons are all square sized, (16x16, 32x32, etc). While they used to be pretty small (around 16px), on new devices they can be over 200 pixels! Since there are a lot of possibilities, I’ll leave it up to you to decide how big you want to make it. I’m going to choose 32 pixels.

Let’s add a canvas to the document. And while we’re at it, we might as well add a icon element too:

var iconSize = 32;

// create canvas
var canvas = document.createElement("canvas");
canvas.width = iconSize;
canvas.height = iconSize;

// create icon
var icon = document.createElement("link");
icon.type = "image/x-icon";
icon.rel = "shortcut icon";
document.getElementsByTagName("head")[0].appendChild(icon);


Sure, we have a canvas now, but we can’t see anything! There are simple ways to fill a canvas, whether it’s through an image source or a fillRect call. But for our purposes, we need pixel-level image manipulation. Let’s run through the canvas and fill all the pixels with red.

To do that, we need to create a context for the canvas. We’re going to be using the 2D context - the HTML5 canvas element also supports 3D rendering through WebGL. Once we have the context, we’re going to create some empty image data. This is what will allow us to manipulate the pixels directly.

var ctx = canvas.getContext("2d");
var ctxImageData = ctx.createImageData(iconSize, iconSize);


Now we’re ready to iterate through the pixels. Each pixel is made up of 4 components: r, g, b, a. These stand for red, green, blue, and alpha (the transparency of the pixel). Each component is one element of the data array that the context created for us. That means that we need to loop over every 4th array member.

for (var i = 0; i < ctxImageData.data.length; i += 4)
{
ctxImageData.data[i] = 255; // red
ctxImageData.data[i + 1] = 0; // green
ctxImageData.data[i + 2] = 0; // blue
ctxImageData.data[i + 3] = 255; // alpha
}


After we’re done assigning red to the pixel, we need to apply the new pixel’s color to the canvas, using the context. To do this we need the pixel’s location on the canvas. Rather than do a two dimensional for loop, I prefer to derive the location using a little math.

Imagine the pixel data set up in this 4x4 grid.

(0, 0)  (1, 0)  (2, 0)  (3, 0)
(0, 1) (1, 1) (2, 1) (3, 1)
(0, 2) (1, 2) (2, 2) (3, 2)
(0, 3) (1, 3) (2, 3) (3, 3)


If you were to label these by array element, it would look like this:

0  1  2  3
4 5 6 7
8 9 10 11
12 13 14 15


Now, what is the result when you integer divide each value by the size (4)?

0  0  0  0
1 1 1 1
2 2 2 2
3 3 3 3


Yep, it’s the y value of the pixel data! And now, for the modulus division (%) by the size (4):

0  1  2  3
0 1 2 3
0 1 2 3
0 1 2 3


You guessed it, this represents the x value of the pixel data.

The last step of our derivation is to account for the fact that we’re looping 4 times for every pixel. Since we’re incrementing at 4 times the number of pixels, we can divide the incrementer by 4 to get the correct pixel. Then we can finally update the canvas with the new pixel data. Let’s update our for loop:

for (var i = 0; i < ctxImageData.data.length; i += 4)
{
ctxImageData.data[i] = 255; // red
ctxImageData.data[i + 1] = 0; // green
ctxImageData.data[i + 2] = 0; // blue
ctxImageData.data[i + 3] = 255; // alpha

var currentX = (i / 4) % iconSize;
var currentY = (i / 4) / iconSize;

// apply the data to the canvas
ctx.putImageData(ctxImageData, 0, 0, currentX, currentY, iconSize, iconSize);
}


Still no image! Don’t worry, there’s only one more step before we see results. Remember that icon element we made at the beginning of this section? We need to give that an updated link with the new canvas info. Put this after the for loop:

// update the page with the updated canvas
icon.href = canvas.toDataURL("image/x-icon"); // set the icon


If you’re using JSFiddle, you also need to add the canvas to the document before you can see it. You can do this at the top of the script after you create the canvas.

...

// create canvas
var canvas = document.createElement("canvas");
canvas.width = iconSize;
canvas.height = iconSize;
document.body.appendChild(canvas);


Behold! A red square! Pretty sweet, huh? To recap, we just:

  • Created a canvas and icon from JavaScript
  • Assigned the canvas pixel data for each pixel
  • Linked the icon to the canvas to display it

And here’s the finished code for this section:

var iconSize = 32;

// create canvas
var canvas = document.createElement("canvas");
canvas.width = iconSize;
canvas.height = iconSize;
// comment this if you don't want to show the canvas
document.body.appendChild(canvas);

// create icon
var icon = document.createElement("link");
icon.type = "image/x-icon";
icon.rel = "shortcut icon";
document.getElementsByTagName("head")[0].appendChild(icon);

var ctx = canvas.getContext("2d");
var ctxImageData = ctx.createImageData(iconSize, iconSize);

for (var i = 0; i < ctxImageData.data.length; i += 4)
{
ctxImageData.data[i] = 255; // red
ctxImageData.data[i + 1] = 0; // green
ctxImageData.data[i + 2] = 0; // blue
ctxImageData.data[i + 3] = 255; // alpha

var currentX = (i / 4) % iconSize;
var currentY = (i / 4) / iconSize;

// apply the data to the canvas
ctx.putImageData(ctxImageData, 0, 0, currentX, currentY, iconSize, iconSize);
}

// update the page with the updated canvas
icon.href = canvas.toDataURL("image/x-icon"); // set the icon

Creating Gradients

We have a working canvas. Let’s generate our images! The plan for this program is to interpolate between two images. I’m a big fan of gradients, so each image is going to be a linear gradient that stretches diagonally across the canvas. The gradient will be defined by two colors. Let’s define those two colors for the first image now. I’m going to pick two of my own colors, but you can pick whatever colors you want. Just make sure the alpha value is 255 so you can see it. Put this code above the for loop:

// define colors
var image1Color1 = { r: 104, g: 207, b: 172, a: 255 };
var image1Color2 = { r: 241, g: 171, b: 251, a: 255 };


The next step is to interpolate between these two colors. Interpolation is basically getting a point between two different numbers. So an interpolation of 0.5 between 1 and 2 would return 1.5, or halfway between 1 and 2. It’s a fairly common function in many code libraries (often called lerp, or linear interpolate), but JavaScript does not have one built into their Math library. Add this helper function to the bottom of your code:

// helper function - interpolates between num a and num b at t
function lerp(a, b, t)
{
return a + t * (b - a);
}


To do the interpolation, we’re going to need to find the parameter t that defines how much to interpolate between the two colors. Basically, if t is 0, we’ll be displaying color 1, and if t is 1, we’ll be displaying color 2. We’ll be calling this parameter lerpValue for readability. For our first image, (0, 0) will correlate with a lerpValue of 0, and (iconSize - 1, iconSize - 1) will correlate with a lerpValue of 1. This means that as x and y grow, lerpValue will increase relative to the icon size. Make sure to divide by two to account for the equal contribution of x and y. Let’s make iconSize - 1 its own variable called maxIndex, and within our code, make the following changes:

...

var maxIndex = iconSize - 1;

for (var i = 0; i < ctxImageData.data.length; i += 4)
{
ctxImageData.data[i] = 255; // red
ctxImageData.data[i + 1] = 0; // green
ctxImageData.data[i + 2] = 0; // blue
ctxImageData.data[i + 3] = 255; // alpha

var currentX = (i / 4) % iconSize;
var currentY = (i / 4) / iconSize;

var lerpValue = (currentX + currentY) / maxIndex * 0.5;

// apply the data to the canvas
ctx.putImageData(ctxImageData, 0, 0, currentX, currentY, iconSize, iconSize);
}


Now that we have the t parameter for the lerp function, we need to apply it to the pixels. It’s as simple as lerping between the respective components of color 1 and color 2 using the lerpValue. We’re going to need to rearrange some things in the for loop, as well:

...

var maxIndex = iconSize - 1;

for (var i = 0; i < ctxImageData.data.length; i += 4)
{
var currentX = (i / 4) % iconSize;
var currentY = (i / 4) / iconSize;

var lerpValue = (currentX + currentY) / maxIndex * 0.5;

ctxImageData.data[i] = lerp(image1Color1.r, image1Color2.r, lerpValue); // red
ctxImageData.data[i + 1] = lerp(image1Color1.g, image1Color2.g, lerpValue); // green
ctxImageData.data[i + 2] = lerp(image1Color1.b, image1Color2.b, lerpValue); // blue
ctxImageData.data[i + 3] = lerp(image1Color1.a, image1Color2.a, lerpValue); // alpha

// apply the data to the canvas
ctx.putImageData(ctxImageData, 0, 0, currentX, currentY, iconSize, iconSize);
}


You should get this awesome gradient as your favicon (or canvas image) now.

For the second image I want to shake things up and make the gradient go from the bottom left of the image to the top right of the image. To do this, we’re going to need to change lerpValue. Since the rest of the code for the second image will be virtually the same except for that one variable, let’s try changing the direction of our current gradient.

This time around, a lerpValue of 0 will correlate with (0, maxIndex), while a lerpValue of 1 will correlate with (maxIndex, 0). This makes it a bit harder to find the relationship between the two. For the x component, things have stayed the same: as x increases, lerpValue increases. But for the y component, things have inverted. The y value starts at maxIndex and decreases. Let’s split up our calculation into two parts:

var xContribution = currentX / maxIndex;
var yContribution = (maxIndex - currentY) / maxIndex;


Using the power of math, we can combine the two into our final statement:

var lerpValue = (currentX + maxIndex - currentY) / maxIndex * 0.5;


Now you can see how we rotated the gradient! We now have all the image-making tools we need to move on the the next step: making it dynamically fade between two images! To recap, we:

  • Created color objects that represent our gradient.
  • Interpolated between those colors when drawing to the canvas to create the gradient.
  • Rotated the gradient by changing the interpolation parameter.

Here’s the finished code for this section:

var iconSize = 32;

// create canvas
var canvas = document.createElement("canvas");
canvas.width = iconSize;
canvas.height = iconSize;
// comment this if you don't want to show the canvas
document.body.appendChild(canvas);

// create icon
var icon = document.createElement("link");
icon.type = "image/x-icon";
icon.rel = "shortcut icon";
document.getElementsByTagName("head")[0].appendChild(icon);

var ctx = canvas.getContext("2d");
var ctxImageData = ctx.createImageData(iconSize, iconSize);

// define colors
var image1Color1 = { r: 104, g: 207, b: 172, a: 255 };
var image1Color2 = { r: 241, g: 171, b: 251, a: 255 };

var maxIndex = iconSize - 1;

for (var i = 0; i < ctxImageData.data.length; i += 4)
{
var currentX = (i / 4) % iconSize;
var currentY = (i / 4) / iconSize;

//var lerpValue = (currentX + currentY) / maxIndex * 0.5;
var lerpValue = (currentX + maxIndex - currentY) / maxIndex * 0.5;

ctxImageData.data[i] = lerp(image1Color1.r, image1Color2.r, lerpValue); // red
ctxImageData.data[i + 1] = lerp(image1Color1.g, image1Color2.g, lerpValue); // green
ctxImageData.data[i + 2] = lerp(image1Color1.b, image1Color2.b, lerpValue); // blue
ctxImageData.data[i + 3] = lerp(image1Color1.a, image1Color2.a, lerpValue); // alpha

// apply the data to the canvas
ctx.putImageData(ctxImageData, 0, 0, currentX, currentY, iconSize, iconSize);
}

// update the page with the updated canvas
icon.href = canvas.toDataURL("image/x-icon"); // set the icon

// helper function - interpolates between num a and num b at t
function lerp(a, b, t)
{
return a + t * (b - a);
}

Making It Dynamic

Making the images is great and all, but making it all dynamic is so much better! We’re going to create 2 different gradients using the method that we currently have, save them into data arrays, then fade between them over time.

We need to create two arrays that will hold the data for our images. We can’t just assign them to the value of ctxImageData, since that will link the data by reference and will overwrite the other images if we try to manipulate the data. The data is stored in the Uint8ClampedArray type. Put this after ctxImageData is declared:

var image1Data = new Uint8ClampedArray(ctxImageData.data);
var image2Data = new Uint8ClampedArray(ctxImageData.data);


One more thing, we need to define the colors for our second image! Again, you can choose any colors you want, but these are the ones I’m going to choose:

// define colors
var image1Color1 = { r: 104, g: 207, b: 172, a: 255 };
var image1Color2 = { r: 241, g: 171, b: 251, a: 255 };
var image2Color1 = { r: 64, g: 133, b: 191, a: 255 };
var image2Color2 = { r: 249, g: 168, b: 144, a: 255 };


Now, let’s create the images as we have before, but store them in our new data arrays this time.

// pattern - top left to bottom right gradient
for (var i = 0; i < image1Data.length; i += 4)
{
// get the current x and y coordinate for the image
var currentX = (i / 4) % iconSize;
var currentY = (i / 4) / iconSize;
// defines the gradient direction
var lerpValue = (currentX + currentY) / maxIndex * 0.5;

// interpolate between the two colors for image 1
image1Data[i] = lerp(image1Color1.r, image1Color2.r, lerpValue);
image1Data[i + 1] = lerp(image1Color1.g, image1Color2.g, lerpValue);
image1Data[i + 2] = lerp(image1Color1.b, image1Color2.b, lerpValue);
image1Data[i + 3] = lerp(image1Color1.a, image1Color2.a, lerpValue);
}

// pattern - bottom left to top right gradient
for (var i = 0; i < image2Data.length; i += 4)
{
// get the current x and y coordinate for the image
var currentX = (i / 4) % iconSize;
var currentY = (i / 4) / iconSize;
// defines the gradient direction
var lerpValue = (currentX + maxIndex - currentY) / maxIndex * 0.5;

// interpolate between the two colors for image 2
image2Data[i] = lerp(image2Color1.r, image2Color2.r, lerpValue);
image2Data[i + 1] = lerp(image2Color1.g, image2Color2.g, lerpValue);
image2Data[i + 2] = lerp(image2Color1.b, image2Color2.b, lerpValue);
image2Data[i + 3] = lerp(image2Color1.a, image2Color2.a, lerpValue);
}


Note that for the first image, we use the top left to bottom right gradient direction, and for the second image, we use the bottom left to top right gradient direction.

We still have the loop over the context’s image data. This should no longer be using image 1’s data, but it should actually be interpolating between image 1 and image 2. Let’s update the loop to reflect this:

for (var i = 0; i < ctxImageData.data.length; i += 4)
{
var currentX = (i / 4) % iconSize;
var currentY = (i / 4) / iconSize;

var imageLerpValue = 0; // will be updated later

// use lerp time to interpolate between the images
ctxImageData.data[i] = lerp(image1Data[i], image2Data[i], lerpValue);
ctxImageData.data[i + 1] = lerp(image1Data[i + 1], image2Data[i + 1], lerpValue);
ctxImageData.data[i + 2] = lerp(image1Data[i + 2], image2Data[i + 2], lerpValue);
ctxImageData.data[i + 3] = lerp(image1Data[i + 3], image2Data[i + 3], lerpValue);

// apply the data to the canvas
ctx.putImageData(ctxImageData, 0, 0, currentX, currentY, iconSize, iconSize);
}


If you look at the imageLerpValue for this loop, you will notice that I just set it to 0. We’re going to change this later, but for now you can preview what the fade is going to look like if you set imageLerpValue to values between 0 and 1. For example, this is what image 2 looks like (imageLerpValue set to 1):

What we want to do is change imageLerpValue over time to get a smooth fade between the images. That means that we need to call that loop repeatedly. Let’s make a variable that determines how often we call that for loop. Let’s also place the for loop in its own function. That way, we can easily call it from setInterval(), which calls a function repeatedly after a set amount of time. Make sure to also put the icon update in this function.

var updateRate = 500;
setInterval(function() { update(); }, updateRate); // update the image at the set interval

function update()
{
for (var i = 0; i < ctxImageData.data.length; i += 4)
{
var currentX = (i / 4) % iconSize;
var currentY = (i / 4) / iconSize;

var imageLerpValue = 1; // this will be updated later

// use lerp time to interpolate between the images
ctxImageData.data[i] = lerp(image1Data[i], image2Data[i], imageLerpValue);
ctxImageData.data[i + 1] = lerp(image1Data[i + 1], image2Data[i + 1], imageLerpValue);
ctxImageData.data[i + 2] = lerp(image1Data[i + 2], image2Data[i + 2], imageLerpValue);
ctxImageData.data[i + 3] = lerp(image1Data[i + 3], image2Data[i + 3], imageLerpValue);

// apply the data to the canvas
ctx.putImageData(ctxImageData, 0, 0, currentX, currentY, iconSize, iconSize);
}

// update the page with the updated canvas
icon.href = canvas.toDataURL("image/x-icon"); // set the icon
}


The updateRate is in milliseconds, so the update() function is being called every half second.

To update the image over time, we need some method of tracking how long the program has been running for. If we can get a number that increases over time, we can pass that into a sin function and get a nice smooth fade like so:

To track the time, we need a time variable which tracks the accumulated time, and a lastTime variable which holds the last (date) time calculated. We’ll also add a fadeMultiplier that will allow us to change how fast the fade between the two images is. Then we can calculate the delta time, or the change in time, since the last time the function was called and add it to the accumulator.

var time = 0; // accumulated time since program start
var lastTime = 0; // the time of the last update call
var fadeMultiplier = 0.0002; // defines how fast the icon will fade between image 1 and image 2

function update()
{
var deltaTime = new Date().getTime() - lastTime;
time += deltaTime * fadeMultiplier;
lastTime = new Date().getTime(); // store the time for the next function call

...


To get our smooth fade, we can put the time variable in the sin() function. sin() returns a number between -1 and 1, and our interpolation wants the range 0, 1, so we can do a little math to change the range of the sin function:

var imageLerpValue = (Math.sin(time) + 1) / 2; // change range from -1, 1 to 0, 1


We can pull imageLerpValue out of the for loop (but still within update) so we don’t have to keep recalculating it during the image drawing.

Now we should have a (slowly) fading icon! Remember, you can change the fadeMultiplier variable (along with updateRate if it starts to look choppy) to change how fast the icon fades.

One more thing before I wrap up this section: you may notice the canvas is blank for a short amount of time when the page loads. This happens because setInterval() waits before it calls the function for the first time. So before the setInterval call in your code, add an initial update() call:

...

var updateRate = 2000;
update(); // initial update call
setInterval(function() { update(); }, updateRate); // update the image at the set interval

...


Alright, so we have a fully dynamic, procedural .favicon now! The icon is pretty much done at this point. The last thing that’s left is some polish! To recap, we:

  • Created data arrays for our two images and changed the canvas loop to interpolate between them.
  • Called the canvas drawing at a set interval.
  • Kept an accumulator of time passed and used a sin wave to determine the interpolation between the images.

Here’s the code up through this section:

var iconSize = 32;

// create canvas
var canvas = document.createElement("canvas");
canvas.width = iconSize;
canvas.height = iconSize;
// comment this if you don't want to show the canvas
document.body.appendChild(canvas);

// create icon
var icon = document.createElement("link");
icon.type = "image/x-icon";
icon.rel = "shortcut icon";
document.getElementsByTagName("head")[0].appendChild(icon);

var ctx = canvas.getContext("2d");
var ctxImageData = ctx.createImageData(iconSize, iconSize);

var image1Data = new Uint8ClampedArray(ctxImageData.data);
var image2Data = new Uint8ClampedArray(ctxImageData.data);

// define colors
var image1Color1 = { r: 104, g: 207, b: 172, a: 255 };
var image1Color2 = { r: 241, g: 171, b: 251, a: 255 };
var image2Color1 = { r: 64, g: 133, b: 191, a: 255 };
var image2Color2 = { r: 249, g: 168, b: 144, a: 255 };

var maxIndex = iconSize - 1;

// pattern - top left to bottom right gradient
for (var i = 0; i < image1Data.length; i += 4)
{
// get the current x and y coordinate for the image
var currentX = (i / 4) % iconSize;
var currentY = (i / 4) / iconSize;
// defines the gradient direction
var lerpValue = (currentX + currentY) / maxIndex * 0.5;

// interpolate between the two colors for image 1
image1Data[i] = lerp(image1Color1.r, image1Color2.r, lerpValue);
image1Data[i + 1] = lerp(image1Color1.g, image1Color2.g, lerpValue);
image1Data[i + 2] = lerp(image1Color1.b, image1Color2.b, lerpValue);
image1Data[i + 3] = lerp(image1Color1.a, image1Color2.a, lerpValue);
}

// pattern - bottom left to top right gradient
for (var i = 0; i < image2Data.length; i += 4)
{
// get the current x and y coordinate for the image
var currentX = (i / 4) % iconSize;
var currentY = (i / 4) / iconSize;
// defines the gradient direction
var lerpValue = (currentX + maxIndex - currentY) / maxIndex * 0.5;

// interpolate between the two colors for image 2
image2Data[i] = lerp(image2Color1.r, image2Color2.r, lerpValue);
image2Data[i + 1] = lerp(image2Color1.g, image2Color2.g, lerpValue);
image2Data[i + 2] = lerp(image2Color1.b, image2Color2.b, lerpValue);
image2Data[i + 3] = lerp(image2Color1.a, image2Color2.a, lerpValue);
}

var updateRate = 500;
update(); // initial update call
setInterval(function() { update(); }, updateRate); // update the image at the set interval

var time = 0; // accumulated time since program start (multiplied by fadeMultiplier)
var fadeMultiplier = 0.0002; // defines how fast the icon will fade between image 1 and image 2
var lastTime = 0; // the time of the last frame

function update()
{
// get time since script started, increasing at rate defined by multiplier
var deltaTime = new Date().getTime() - lastTime;
time += deltaTime * fadeMultiplier; // add the time since the last frame with fade modifier
lastTime = new Date().getTime(); // store the time for the next function call
var imageLerpValue = (Math.sin(time) + 1) / 2; // change range from -1, 1 to 0, 1
for (var i = 0; i < ctxImageData.data.length; i += 4)
{
var currentX = (i / 4) % iconSize;
var currentY = (i / 4) / iconSize;

// use lerp time to interpolate between the images
ctxImageData.data[i] = lerp(image1Data[i], image2Data[i], imageLerpValue);
ctxImageData.data[i + 1] = lerp(image1Data[i + 1], image2Data[i + 1], imageLerpValue);
ctxImageData.data[i + 2] = lerp(image1Data[i + 2], image2Data[i + 2], imageLerpValue);
ctxImageData.data[i + 3] = lerp(image1Data[i + 3], image2Data[i + 3], imageLerpValue);

// apply the data to the canvas
ctx.putImageData(ctxImageData, 0, 0, currentX, currentY, iconSize, iconSize);
}

// update the page with the updated canvas
icon.href = canvas.toDataURL("image/x-icon"); // set the icon
}

// helper function - interpolates between num a and num b at t
function lerp(a, b, t)
{
return a + t * (b - a);
}

Adding Feathered Edges and Custom Size

Now on to the final step, polish! When I was making the icon originally, I wasn’t happy with the sharp square shape. I wanted something more circular. So, I decided to use some math to modify the transparency of the pixels as they were being generated.

To generate a circular shape, pixels that are far away from the center need to become more transparent. We need a formula to track the distance from the center. Since we don’t care about the actual distance, but only a relative distance to the center, we’ll use the distance squared formula initially:

We need to have a variable that represents the maximum distance a pixel can be from the center. That would be at (0, 0) or (maxIndex, maxIndex), which is a difference of (maxIndex / 2, maxIndex / 2) in both cases. Thus, our maximum distance is:

var maxDistance = Math.pow(maxIndex / 2, 2) * 2;


Put this above the update function.

Next we need to calculate the distance each pixel is from the center during the canvas drawing. We know the center point will be (maxIndex / 2, maxIndex / 2), so the distance is just the absolute value of the x and y from maxIndex / 2. Make sure this goes in the update() function under the currentX and currentY variables and above the pixel manipulation:

// get distance from the center
var distX = Math.abs(maxIndex / 2 - currentX);
var distY = Math.abs(maxIndex / 2 - currentY);


We need to take these distances and map them into a single value that represents the distance from the center. 1 would be the most transparent and at the center, which 0 would be at the corners and have no transparency. We’ll take the distance formula we looked at before and divide it by the maximum distance. However, this will give us a value of 1 for the furthest away pixels, so we’ll make sure to invert the result:

// map the distance to a range from 1 to 0, where 1 = center and 0 = far away
var distRatio = 1 - ((Math.pow(distX, 2) + Math.pow(distY, 2)) / maxDistance);


To see these changes, change the alpha pixel to use the distance ratio multiplied by 255 - this will correctly scale the alpha color. Here’s the full for loop to make sure we’re on the same page:

for (var i = 0; i < ctxImageData.data.length; i += 4)
{
var currentX = (i / 4) % iconSize;
var currentY = (i / 4) / iconSize;

// get distance from the center
var distX = Math.abs(maxIndex / 2 - currentX);
var distY = Math.abs(maxIndex / 2 - currentY);

// map the distance to a range from 1 to 0, where 1 = center and 0 = far away
var distRatio = 1 - ((Math.pow(distX, 2) + Math.pow(distY, 2)) / maxDistance);

// use lerp time to interpolate between the images
ctxImageData.data[i] = lerp(image1Data[i], image2Data[i], imageLerpValue);
ctxImageData.data[i + 1] = lerp(image1Data[i + 1], image2Data[i + 1], imageLerpValue);
ctxImageData.data[i + 2] = lerp(image1Data[i + 2], image2Data[i + 2], imageLerpValue);
ctxImageData.data[i + 3] = distRatio * 255;

// apply the data to the canvas
ctx.putImageData(ctxImageData, 0, 0, currentX, currentY, iconSize, iconSize);
}


It’s a good start, but not where we want it yet. We can see that the pixels do get more transparent as they get closer to the corners, but we need more fine-tuned control over the fading to get our desired result.

We’re going to define two settings to modify our transparency fade: falloffPower and falloffSize. The falloffPower determines how sharply the transparency fades. The falloffSize determines the modifies the maxDistance, causing the fade to end at a closer or farther distance.

var falloffPower = 10; // defines the sharpness of the icon
var falloffSize = 0.75; // defines the size of the icon


We’re going to modify maxDistance now. First, we’re going to multiply the maxDistance by the falloffSize to have the effect of making the icon smaller or larger. Then, we’re going to going to take maxDistance to the power of falloffPower, which may sound counterintuitive but as long as we apply this to distRatio as well, we’ll maintain the correct relationship between the two. maxDistance now looks like this:

var maxDistance = Math.pow(Math.pow(maxIndex / 2, 2) * 2 * falloffSize, falloffPower);


And now let’s add the pow to distRatio as well:

var distRatio = 1 - (Math.pow(Math.pow(distX, 2) + Math.pow(distY, 2), falloffPower) / maxDistance);


Nice! We have our shape where we want it, but it still looks a bit blurry. Let’s clamp the alpha ratio to 1 unless it falls below 0.5.

if (distRatio > 0.5)
distRatio = 1;


That looks good to me. That’s our finished icon!

To recap, we:

  • Faded the pixels during canvas drawing using a maximum distance.
  • Added falloff power to control the sharpness of the icon edge.
  • Added falloff size to control the size of the icon.

Here’s the final code for the icon:

var iconSize = 32;

// create canvas
var canvas = document.createElement("canvas");
canvas.width = iconSize;
canvas.height = iconSize;
// comment this if you don't want to show the canvas
document.body.appendChild(canvas);

// create icon
var icon = document.createElement("link");
icon.type = "image/x-icon";
icon.rel = "shortcut icon";
document.getElementsByTagName("head")[0].appendChild(icon);

var ctx = canvas.getContext("2d");
var ctxImageData = ctx.createImageData(iconSize, iconSize);

var image1Data = new Uint8ClampedArray(ctxImageData.data);
var image2Data = new Uint8ClampedArray(ctxImageData.data);

// define colors
var image1Color1 = { r: 104, g: 207, b: 172, a: 255 };
var image1Color2 = { r: 241, g: 171, b: 251, a: 255 };
var image2Color1 = { r: 64, g: 133, b: 191, a: 255 };
var image2Color2 = { r: 249, g: 168, b: 144, a: 255 };

var maxIndex = iconSize - 1;

// pattern - top left to bottom right gradient
for (var i = 0; i < image1Data.length; i += 4)
{
// get the current x and y coordinate for the image
var currentX = (i / 4) % iconSize;
var currentY = (i / 4) / iconSize;
// defines the gradient direction
var lerpValue = (currentX + currentY) / maxIndex * 0.5;

// interpolate between the two colors for image 1
image1Data[i] = lerp(image1Color1.r, image1Color2.r, lerpValue);
image1Data[i + 1] = lerp(image1Color1.g, image1Color2.g, lerpValue);
image1Data[i + 2] = lerp(image1Color1.b, image1Color2.b, lerpValue);
image1Data[i + 3] = lerp(image1Color1.a, image1Color2.a, lerpValue);
}

// pattern - bottom left to top right gradient
for (var i = 0; i < image2Data.length; i += 4)
{
// get the current x and y coordinate for the image
var currentX = (i / 4) % iconSize;
var currentY = (i / 4) / iconSize;
// defines the gradient direction
var lerpValue = (currentX + maxIndex - currentY) / maxIndex * 0.5;

// interpolate between the two colors for image 2
image2Data[i] = lerp(image2Color1.r, image2Color2.r, lerpValue);
image2Data[i + 1] = lerp(image2Color1.g, image2Color2.g, lerpValue);
image2Data[i + 2] = lerp(image2Color1.b, image2Color2.b, lerpValue);
image2Data[i + 3] = lerp(image2Color1.a, image2Color2.a, lerpValue);
}

var time = 0; // accumulated time since program start (multiplied by fadeMultiplier)
var fadeMultiplier = 0.0002; // defines how fast the icon will fade between image 1 and image 2
var lastTime = 0; // the time of the last frame

var falloffPower = 10; // defines the sharpness of the icon
var falloffSize = 0.6; // defines the size of the icon
var maxDistance = Math.pow(Math.pow(maxIndex / 2, 2) * 2 * falloffSize, falloffPower);

var updateRate = 500;
update(); // initial update call
setInterval(function() { update(); }, updateRate); // update the image at the set interval

function update()
{
// get time since script started, increasing at rate defined by multiplier
var deltaTime = new Date().getTime() - lastTime;
time += deltaTime * fadeMultiplier; // add the time since the last frame with fade modifier
lastTime = new Date().getTime(); // store the time for the next function call

var imageLerpValue = (Math.sin(time) + 1) / 2; // change range from -1, 1 to 0, 1

for (var i = 0; i < ctxImageData.data.length; i += 4)
{
var currentX = (i / 4) % iconSize;
var currentY = (i / 4) / iconSize;

// get distance from the center
var distX = Math.abs(maxIndex / 2 - currentX);
var distY = Math.abs(maxIndex / 2 - currentY);

// map the distance to a range from 1 to 0, where 1 = center and 0 = far away
var distRatio = 1 - (Math.pow(Math.pow(distX, 2) + Math.pow(distY, 2), falloffPower) / maxDistance);
if (distRatio > 0.5) distRatio = 1;

// use lerp time to interpolate between the images
ctxImageData.data[i] = lerp(image1Data[i], image2Data[i], imageLerpValue);
ctxImageData.data[i + 1] = lerp(image1Data[i + 1], image2Data[i + 1], imageLerpValue);
ctxImageData.data[i + 2] = lerp(image1Data[i + 2], image2Data[i + 2], imageLerpValue);
ctxImageData.data[i + 3] = distRatio * 255;

// apply the data to the canvas
ctx.putImageData(ctxImageData, 0, 0, currentX, currentY, iconSize, iconSize);
}

// update the page with the updated canvas
icon.href = canvas.toDataURL("image/x-icon"); // set the icon
}

// helper function - interpolates between num a and num b at t
function lerp(a, b, t)
{
return a + t * (b - a);
}


I hope you liked the tutorial! May your .favicon’s be ever changing!