The Typekit kit editor provides two versions of the embed code, the default version blocks rendering and the asynchronous version which can produce a flash of unstyled content (FOUC).
In the case of Typekit, I prefer the FOUC over slowing down rendering. The asynchronous code also offers the advantage of your site staying online should Typekit go down.
I decided to spend a little time optimising the asynchronous code Typekit provides. Expanded to use meaningful variable names, the Typekit code is:
(function() {
var config = {
kitId: 'typeKit',
scriptTimeout: 3000
};
var html = document.getElementsByTagName("html")[0];
html.className += " wf-loading";
var timer = setTimeout(function() {
html.className = html.className.replace(/(s|^)wf-loading(s|$)/g, " ");
html.className += " wf-inactive"
}, config.scriptTimeout);
var typekitScript = document.createElement("script"),
done = false;
typekitScript.src = '//use.typekit.net/' + config.kitId + '.js';
typekitScript.type = "text/javascript";
typekitScript.async = "true";
typekitScript.onload = typekitScript.onreadystatechange = function() {
var activeState = this.readyState;
if (done || activeState && activeState != "complete" && activeState != "loaded") return;
done = true;
clearTimeout(timer);
try {
Typekit.load(config)
} catch (exception) {}
};
var firstScript = document.getElementsByTagName("script")[0];
firstScript.parentNode.insertBefore(typekitScript, firstScript)
})();
Note: this code is now out of date as Typekit have implemented many of the optimisations below into their codebase.
Consolidate className
changes
Should Typekit fail to load, the wf-loading
class is replaced with wf-inactive
. The first step is bring the two lines of code into one, and update the regular expression to use the b switch.
html.className = html.className.replace(/bwf-loadingb/g, "")+" wf-inactive";
This produces a substantial speed increase.
Getting the HTML tag
The Typekit code uses getElementsByTagName
to get the HTML tag, we can save a few bytes and get a speed increase with:
var html = document.documentElement;
Declaring variables in one command
The Typekit code declares each variable separately, using the comma separated notation and a bit of shuffling, we can save a few more bytes. Our code now starts with:
(function() {
var config = {
kitId: 'xow3svv',
scriptTimeout: 3000
},
html = document.documentElement,
timer = setTimeout(function() {
html.className = html.className.replace(/bwf-loadingb/g, "")+" wf-inactive"
}, config.scriptTimeout),
typekitScript = document.createElement("script"),
done = false,
firstScript = document.getElementsByTagName("script")[0],
activeState;
html.className += " wf-loading";
//snip
The inserted script element
All browsers presume that a <script> element is JavaScript, so the line setting the type can be dropped and save 30 bytes in the final version of the code. While making changes to the inserted element, the async property is also assumed for inserted scripts in most browsers and can be dropped too.
typekitScript.src = '//use.typekit.net/' + config.kitId + '.js';
If you need to support Firefox 3.6, Opera 10+ the async property does make a difference but its value does not. In this case you can use
typekitScript.async = typekitScript.src = '//use.typekit.net/' + config.kitId + '.js';
Passing commonly used values
Both document
and the string 'script'
are used multiple times throughout the code. By passing these to the anonymous function we can aid compression later.
The className
property of the HTML tag is referenced a number of times, by switching to the array notation, html['className']
we can save a few bytes by passing className as a string too. While we’re about it, passing the string ' wf-'
will also reduce the code base.
Our anonymous function is now declared as:
(function( document, script, className, _wf ) {
//snip
})( document, 'script', 'className', ' wf-' );
Compressing the variables
Replacing the meaningful variable names, the new code becomes:
(function( P, W, C, c ) {
var t = { kitId: 'xow3svv',scriptTimeout: 3000 },
y = P.documentElement,
p = P.createElement(W),
e = false,
k = P.getElementsByTagName(W)[0],
i,
T = setTimeout(function() {
y[C] = y[C].replace(/bwf-loadingb/g, "")+ c + "inactive"
}, t.scriptTimeout);
y[C] += c + "loading";
p.src = '//use.typekit.net/' + t.kitId + '.js';
p.onload = p.onreadystatechange = function() {
i = this.readyState;
if (e || i && i != "complete" && i != "loaded") return;
e = true;
clearTimeout(T);
try {
Typekit.load(t)
} catch (exception) {}
};
k.parentNode.insertBefore(p, k)
})( document, 'script', 'className', ' wf-' );
Keeping the config code on a single line for clarity, the compressed version of the code is
(function(P,W,C,c){
var t={kitId: 'xow3svv',scriptTimeout: 3000},
y=P.documentElement,p=P.createElement(W),e=false,k=P.getElementsByTagName(W)[0],i,T=setTimeout(function(){y[C]=y[C].replace(/bwf-loadingb/g,"")+c+"inactive"},t.scriptTimeout);y[C]+=c+"loading";p.src='//use.typekit.net/'+t.kitId+'.js';p.onload=p.onreadystatechange=function(){i=this.readyState;if(e||i&&i!="complete"&&i!="loaded")return;e=true;clearTimeout(T);try{Typekit.load(t)}catch(x){}};k.parentNode.insertBefore(p,k)})(document,'script','className',' wf-');
Our finalised version of the code is 180 bytes smaller than the original code and uses a couple of small optimisations to run quicker than the original.
I’ve made up a gist with the optimised code to show each step.
Obviously this post was inspired by Mathias Bynens’ similar write up for the Google Analytics code.
Updates
- Feb 9, 2014: Remove wf-inactive’s dependance on wp-loading based on Typekit feedback below.
Thanks for these suggestions. We’ve taken most of them and incorporated them into our async embed code. We didn’t use the
str.replace
optimisation because it actually changes the behaviour of the code. Your replacement assumeswf-loading
is always present, but this is not always the case. We also didn’t use thescript
,className
, etc. optimisation, because we think it decreases the legibility of the code.I think we used all the other suggestions or at least some variation of them. Great work! Again, thanks a lot, we love community feedback!