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 thehref
, 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:
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.