Simple JavaScript Function To Include CSS

This article explains how to write a simple JavaScript function to include CSS files and notify when the loading of the file is complete. The issue is that most browsers do not fire a load event when the link element finishes processing the included CSS rules. And while many libraries have mechanisms for notifying when the CSS is included, I believe this technique is lighter and more elegant. A special thanks goes out to the UX team at Facebook.com who first described this technique to me.

How to do it…

The JavaScript function you will need is:

function includeCSS(conf) {
	if (! (conf && conf.href && conf.name && conf.fx)) {
		throw new Error("Missing configuration for includeCSS functions.");
	}
	
	var pfx = conf.prefix || js_,
		id = pfx + conf.id,
		elEvalNode = document.createElement(div),
		elLinkNode = document.createElement(link),
		iIntervalId;

	if (includeCSS.oIncludedField[id] && ! conf.force) {return false;}
	includeCSS.oIncludedField[id] = true;

	// setup eval node
	elEvalNode.id = id;
	elEvalNode.visiblity = hidden;
	elEvalNode.className = conf.className;
	includeCSS.elBody.appendChild(elEvalNode);

	// setup link node
	elLinkNode.charset = conf.charset || "utf-8";
	elLinkNode.href = conf.href;
	elLinkNode.rel = "stylesheet";
	elLinkNode.type = "text/css";
	includeCSS.elHead.appendChild(elLinkNode);

	// start polling
	iIntervalId = setInterval(function() {
		if (0 < elEvalNode.offsetHeight) {
			clearInterval(iIntervalId);
			elEvalNode.parentNode.removeChild(elEvalNode);
			conf.fx.call(conf.ctx || this, conf.id, conf.data);
		}
	}, 250);

	return true;
}

// some global variables used by the function
includeCSS.elBody = document.getElementsByTagName("body")[0];
includeCSS.elHead = document.getElementsByTagName("head")[0];
includeCSS.oIncludedField = {};

An example CSS files would be something like the following:

body {
	background-color: #333;
	color: #F00;
}

p {
	font-size: 120%;
}

a {
	color: #900;
}

h1 {
	font-variant: small-caps;
}

.actions {
	background-color: #999;
	border: 1px solid #666;
}

.copy {
	color: #C33;
}

/* this style notifies the JavaScript that file is loaded
and should be the last rule in the CSS file */

#js_myCSSFileName {
	height: 10px;
}

Then to actually use the include function call:

function handleCallback(sId, oData) {
	alert("The CSS file (" + sId + ") has finished loading.");
}
if (! includeCSS({href:pathToFile/myCSSFileName.css, id:myId, fx:handleCallback})) {
	alert(CSS file ( + sFileName + ) is already included.);
}

How it works…

The JavaScript function requires a single configuration object as its only argument. This object must have the href, id, and fx properties defined, and may optionally define prefix, className, charset, ctx, and data. The href property is the location of the CSS file to include, the id property is the name for the rule you will be using to determine that the CSS has finished loading, and the fx property is the success callback function. The prefix property is defaulted to js_ and is the prefix to apply to the id property for the notifying CSS rule. The className property allows developers to specify a className that should be applied to the div element in case they need to remove global styles (like borders). The charset property defaults to UTF-8 and is the character set used by the CSS file. The ctx property is the execution context of the callback function and the data property is an object to pass as the second argument to the callback function; both default to undefined.

When the includeCSS function is called it first checks to see if the CSS file has already been included using this function, returning false when it has and true when it has not been. If it has not been included, an empty div element is appended to the body element, and a link element is appended to the head element. The link element will cause the browser to begin downloading and evaluating the rules of the CSS file. The div element will have the id attribute of js_idProperty and a height of ZERO, because it lacks content. The last rule in the included CSS file needs to target the div element using #js_idProperty and adjust its height to something greater than ZERO. The last step of the includeCSS function is to start an interval callback to evaluate when the div element has a height greater than ZERO. When this happens the callback function is executed.

Putting it all together, the developer calls the includeCSS function, which fetches the CSS file and appends a div element to the page. The last rule of the CSS file should update the height of the appended div element, notifying the JavaScript that the CSS has finished loading. When this happens a callback function is executed and the developer is notified that their styles have completed loading and rendering. You can play with this function using the following test page:

Test Page

Theres more…

Since a configuration object is used as the only argument to the includeCSS function, it is easy to augment. Probably the most needed augmentations are a timeout and a failure callback function. The former would fire if the interval hasnt finished after a specified amount of time and the latter would fire if the CSS file was not found.

One downside to this technique is it only works on CSS files under your control, since you have to be able to add a rule to the end of the CSS document. Although, it is not the most efficient solution, you can work around this issue by using a server-side proxy that fetches remote CSS and appends a rule to the end of the file.