Dynamic favicon depending on tab focus — a use case for promises and object URLs

Image for post
Image for post

Most websites have static favicons. Let’s use cool Javascript features to decorate a web page with an unusual tab.

Image for post
Image for post

A favicon is the icon displayed before the title of the web page opened in a browser tab. The favicon is specified in a link tag in the HTML head. The href attribute of the link is assigned the URL of the icon.

<link rel="icon" type="image/svg+xml" href="imgs/favicon.svg">

To change the favicon, its enough to replace the href value . In this tutorial for beginners I first explain how to make a favicon depend on user-triggered events such as the change of the tab focus. But changing favicons is not the only or main subject. I use this simple visual goal to introduce some useful Javascript and HTML features.

The code of the sample pages with dynamic favicons can be downloaded from https://github.com/marianc000/favicon

SVG image as favicon

Compared to the other acceptable image formats, SVG favicons might be the easiest to design. Their advantage is that they can include CSS rules, which facilitates their design and styling. The code of my two sample red and white favicons is identical except for their background-color and fill (SVG analog of color property) CSS properties.

<svg xmlns="http://www.w3.org/2000/svg" width="87" height="41"  >
<style>
text {
font-weight: 800;
font-size: 59px;
font-family: monospace;
fill: OrangeRed;
}
</style>
<text class="small" x="" y="39" textLength="87">ABC</text>
</svg>

Note, without the seemingly insignificant attribute xmlns=”http://www.w3.org/2000/svg" an SVG image does not work as a favicon, but will be rendered if used in an img tag. If ever your SVG favicon is not displayed and there is no error in the console, make sure you included the indispensable xmlns xml namespace attribute.

A href attribute can be modified using el.setAttribute(“href”, url) method, or by setting href property of the link element el.href=url. The only difference is that setting the property is slightly shorter than calling the method, but in both cases both the property and attribute will be automatically adjusted. The same is true for all attributes of HTML tags.

In my sample Javascript module the reference to the link element is retrieved using a CSS selector link[rel=’icon’]. Its href has to be changed in response to the focus and blur events of the window object. The window object represents a browser window, which typically is a browser tab. The focus event occurs when the tab is clicked, whereas the opposite blur event is emitted when the tab is abandoned. To react to the two events, an arrow function setting href to the URL of the red favicon is assigned to window.onfocus property. Another function changing href to the URL of the white image is assigned to window.onblur. I use arrow functions as the event listeners only because of their shorter syntax, in this case (and generally when used outside a class) they do not differ functionally from classical functions. The working 3-line code:

const el = document.querySelector("link[rel='icon']");window.onfocus = () => el.href = "faviconFocused.svg";
window.onblur = () => el.href = "faviconBlurred.svg";

A tiny drawback of the code is that the same images are requested from the network each time the event listeners are invoked. You can see that in the Network tab of the Developer Tools if you use Chrome. It does not impacts the peformance but it is simply unnecessary to reload the same resource.

Let’s complicate the code so that the images are loaded only once and then reused. The code will still modify href property of the link. But instead of assigning the image URLs, the code will assign the previously loaded files.

Data URL

It is contents of an entire file transfromed into a string that can be repeatedly assigned to the attributes, such as src or href, expecting a file URL.

The loaded SVG files, that are essentially text files, can be transformed into data URLs by encoding special characters with encodeURIComponent global function and adding a prefix with their MIME type: 'data:image/svg+xml;utf8,' + encodeURIComponent(svg).

Promises

Since two files have to be loaded, I isolated the common loading-related code into a function fetchSvg.

function fetchSvg(url) {
return fetch(url)
.then(res => res.text())
.then(text => 'data:image/svg+xml;utf8,' + encodeURIComponent(text));
}

Inside the function, global fetch function requests a file from the network. The method does not wait for the file to be completely loaded, it immediately returns a Promise object.

Promises, perhaps the most useful feature of Javascript, allow easy chaining of asynchronous operations. Briefly, Promise is an object that executes the function specified in its constructor, and when the function is completed, it asynchronously calls the callback function specified by its method then. The callback function receives the result of the initial function as the only parameter. then also does not wait for the callback function to complete, it immediately returns another Promise that can also have a callback attached by its then.

When the response to the network request made by fetch is received, the function specified by then receives the Response object. The SVG file in the response body is read as text by asynchronous Response.text() method (see below how to read the same files as binary contents). The last chained callback converts the text into a reusable data URL. fetchSvg function returns the Promise returned by the last then. The callback of the resulting Promise receives the data URL.

I use fetchSvg twice to load individually each of the two images. The attached callbacks use the received data URLs to set the same event listeners as in the first code version. But now the listeners do not trigger new network requests but reuse the images embedded in their data URL strings.

fetchSvg("faviconFocused.svg")
.then(img => window.onfocus = () => el.href = img);
fetchSvg("faviconBlurred.svg")
.then(img => window.onblur = () => el.href = img);

Asynchronous execution

Since I mentioned above, two functions fetch and Response.text() that are executed asynchronously, just in case I add a brief explanation of what it means. Asynchronously executed — means they return immediately, but their callbacks are executed after all the currently executing and scheduled to be executed Javascript commands and browser prosesses are executed. The callbacks essentially take their place in the end of the browser task queue. A short illustration of asynchronous execution.

new Promise(resolve => {
console.log("starting");
resolve("promise result");
}).then(result => console.log(result+" in callback"));
console.log("started");

The output demonstrates that the callback function result => console.log(result+” in callback”) specified by then is executed last.

starting
started
promise result in callback

setTimeout method can be as well used to execute a function asynchronously. The following code prints three strings.

console.log("starting");
setTimeout(()=>console.log("in callback"));
console.log("started");

The string in callback is shown last in the code output.

starting
started
in callback

To grasp the asynchronous execution in Javascript and to witness the difference between Promise and setTimeout you could watch the outstanding presentation The Event Loop by Jake Archibald.

Promise.all method

Very convinient Promise.all method accepts an array of promises and returns a single promise whose callback specified by then is called when all the promises complete their tasks. The callback receives an array with the results of the input promises.

Promise.all shortens my code a bit and makes it more readable.

Promise.all([
fetchSvg("faviconBlurred.svg"),
fetchSvg("faviconFocused.svg")
])
.then(([blurred, focused]) => {
window.onfocus = () => el.href = focused;
window.onblur = () => el.href = blurred;
});

Promise.all waits until both images are loaded by fetchSvg and passes an array with the two data URLs to the callback function that does the same as the first code version. It attaches to window two events handlers that change href property of the link.

Await

The code with promise chains can be rewritten using await operators. Without then the code might be more legible. But if code is tiny as above, the code looks nicer with then. So I do not show here it cumbersome await version, but it is included together with the other samples in the Github.

Object URL

It can serve as an alternative to a data URL. Essentially, it is a URL generated by URL.createObjectURL() method and pointing to a file-like object loaded into the browser memory. A prefix blob: distinguishes an object URL from an ordinary URL, for example:

blob:http://127.0.0.1:8081/0fb4f673-50c4-4b67-98ce-52909eebc164

When the browser encounters such a URL as an attribute value, it does not make a new network request but reuses the loaded file. Thus, the difference between data and object URLs is that a data URLs encodes the contents of an entire file, whereas an object URL is a reference to a file in browser memory.

URL.createObjectURL() expects Blob object as the parameter and does not accept strings. Basically, a Blob represents a file. To make use of URL.createObjectURL() I have to make a change in my function fetchSvg. Instead of loading the SVG files as text, I load them as Blob that is converted into object URL in the next callback.

function fetchSvg(url) {
return fetch(url)
.then(response => response.blob())
.then(blob => URL.createObjectURL(blob));
}

The rest of the code does not change - the received object URLs are passed to the blur and focus event listeners that assign them to the href attribute of the link tag.

The complete code of the five versions of the page changing its favicon can be downloaded from https://github.com/marianc000/favicon

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store