Dynamically Loading Fonts with Javascript

javascript
Published on June 3, 2018

There are some cases where an application must load a font dynamically rather than setting it through CSS through the @font-face rule. An example can be a logo maker application (using HTML5 canvas) in a web page — upon a click on a button the application must download the fonts required. When fonts are downloaded, the user can then proceed to create a logo using the downloaded fonts.

Using the @font-face CSS rule in such a case is not much beneficial because :

  • Browser will lazily load the font as specified in the @font-face CSS rule — only when the font is referenced by a "font-family" somewhere, then the browser will start downloading the font. This may cause more waiting time for the user.
  • The application cannot be sure of the time when all required fonts have been downloaded

On the other hand, downloading fonts with Javascript is beneficial because :

  • The application can start downloading fonts in the background, after the page load
  • The application can know exacly when fonts have been downloaded and can be used

Fonts can be downloaded with Javascript using a new API, the CSS Font Loading API.

Demo

The current font is Tahoma

FontFace and FontFaceSet

Font Loading API exposes 2 objects - FontFace and FontFaceSet. A FontFace (which basically represents a font) needs to be defined and then loaded. After that the font can be added to the list of available fonts (called FontFaceSet), and then can be used normally.

Defining a FontFace

A FontFace object basically represents a font. The CSS @font-face rule implicitly define FontFace objects. With Javascript you can define a FontFace object explicitly.

// first parameter is font name
// second parameter is the path to the font file
var junction_font = new FontFace('Junction Regular', 'url(fonts/junction-regular.woff)');

Optionally, you can also pass other properties like in @font-face CSS :

var junction_font = new FontFace('Junction Regular', 'url(junction-regular.woff)', { style: 'normal', weight: 700 });

Loading a FontFace

After a FontFace object has been defined, you can call its load method to download the font. The load method returns a Promise, which when resolved passes the loaded FontFace.

junction_font.load().then(function(loaded_face) {
	// loaded_face holds the loaded FontFace
}).catch(function(error) {
	// error occurred
});

Adding the FontFace to FontFaceSet

After the loaded FontFace has been obtained, you can add it to a FontFaceSet object. A FontFaceSet object is basically a set of fonts. For the Javascript document object, document.fonts holds the FontFaceSet.

After the loaded FontFace object has been added, you can use this font in the CSS font-family property.

// loaded_face is the loaded FontFace
document.fonts.add(loaded_face);
document.body.style.fontFamily = '"Junction Regular", Arial';

Complete Codes

var junction_font = new FontFace('Junction Regular', 'url(fonts/junction-regular.woff)');
junction_font.load().then(function(loaded_face) {
	document.fonts.add(loaded_face);
  	document.body.style.fontFamily = '"Junction Regular", Arial';
}).catch(function(error) {
	// error occurred
});

Knowing When All Fonts in the Document Have Been Loaded

The ready attribute of a FontFaceSet object is a Promise which is resolved when the document has finished loading all fonts. Using this on document.fonts you can get to know whether all fonts in the document have finished loading.

document.fonts.ready.then(function(font_face_set) {
	// all fonts have been loaded
});

Also note that the Promise returned by the ready attribute is only fulfilled, never rejected.

Knowing the Percentage Progress of the Downloading Font

To know the exact download progress (as a percentage) of the font, you will need to change the way how you download the font. An AJAX request needs to be made to download the font, setting arraybuffer as the response type of the XMLHttpRequest object.

The second parameter of FontFace constructor accepts binary data, in addition to a CSS url value. You can use the response from the AJAX request in it.

The exact percentage of the font downloaded can be found with the progress event of the XHR object.

var request = new XMLHttpRequest();
    
request.addEventListener('readystatechange', function(e) {
	if(request.readyState == 2 && request.status == 200) {
		// Download is being started
	}
	else if(request.readyState == 3) {
		// Download is under progress
	}
	else if(request.readyState == 4) {
		// Downloading has finished

		// request.response holds the binary data of the font

		var junction_font = new FontFace('Junction Regular', request.response);
		junction_font.load().then(function(loaded_face) {
			document.fonts.add(loaded_face);
		  	document.body.style.fontFamily = '"Junction Regular", Arial';
		}).catch(function(error) {
			// error occurred
		});
	}
});

request.addEventListener('progress', function(e) {
	var percent_complete = (e.loaded / e.total)*100;
	console.log(percent_complete);
});

request.responseType = 'arraybuffer';

// Downloading a font from the path
request.open('get', 'fonts/junction-regular.woff'); 

request.send();
In this Tutorial