As I’m piecing together a very comprehensive solution for using custom typefaces online, one of the crucial aspects is determining what browsers support @font-face.
My requirements for this detection were:
No browser userAgent sniffing
No extra HTTP request required
The test must be synchronous, no race conditions or HTTP requests
/*! * isFontFaceSupported - v0.9 - 12/19/2009 * http://paulirish.com/2009/font-face-feature-detection/ * * Copyright (c) 2009 Paul Irish * MIT license */varisFontFaceSupported=(function(){varfontret,fontfaceCheckDelay=100;// IE supports EOT and has had EOT support since IE 5.// This is a proprietary standard (ATOW) and thus this off-spec,// proprietary test for it is acceptable.if(!(!/*@cc_on@if(@_jscript_version>=5)!@end@*/0))fontret=true;else{// Create variables for dedicated @font-face testvardoc=document,docElement=doc.documentElement,st=doc.createElement('style'),spn=doc.createElement('span'),wid,nwid,body=doc.body,callback,isCallbackCalled;// The following is a font, only containing the - character. Thanks Ethan Dunham.st.textContent="@font-face{font-family:testfont;src:url(data:font/opentype;base64,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)}";doc.getElementsByTagName('head')[0].appendChild(st);spn.setAttribute('style','font:99px _,serif;position:absolute;visibility:hidden');if(!body){body=docElement.appendChild(doc.createElement('fontface'));}// the data-uri'd font only has the - characterspn.innerHTML='-------';spn.id='fonttest';body.appendChild(spn);wid=spn.offsetWidth;spn.style.font='99px testfont,_,serif';// needed for the CSSFontFaceRule false positives (ff3, chrome, op9)fontret=wid!==spn.offsetWidth;vardelayedCheck=function(){if(isCallbackCalled)return;fontret=wid!==spn.offsetWidth;callback&&(isCallbackCalled=true)&&callback(fontret);}addEventListener('load',delayedCheck,false);setTimeout(delayedCheck,fontfaceCheckDelay);}functionret(){returnfontret||wid!==spn.offsetWidth;};// allow for a callbackret.ready=function(fn){(isCallbackCalled||fontret)?fn(fontret):(callback=fn);}returnret;})();
isFontFaceSupported()// will return a boolean indicating support// you can also use with a callback,// it will be called 100ms later which is adaquate for Gecko and Webkit to properly use the data-uri'd font.isFontFaceSupported.ready(function(bool){// bool is a boolean that indicates support});
Sorry. :(
You’ll spot the IE conditional compilation in there. I don’t like it either, but I’m unaware of any other workable approach (that doesn’t pull in an .eot) to test for @font-face support. If you have an idea, please share it!
On the approach
I first use the Web Font Optimizer to subset a truetype font to only contain the period (.) character (2.2k file!), then send it through a data URI converter, then chuck it into a style tag. I test the width of a span of text without the custom font, and then again with the custom font. If the values are different, we can assume @font-face is supported and works.
With a trip through the YUI Compressor, the script is 3.5k3.1k. If you have any ideas on bringing that figure down, I’d love to hear ‘em.
Great! Any disadvantages?
Yeah there’s one big one. Both Gecko and Webkit load in a data-uri font asynchronously, so the test may give a false negative if you call isFontFaceSupported() immediately afterwards:
1234
<script src="isFontFaceSupported.min.js"></script><script> if (isFontFaceSupported()) ... // this may report a false negative.
// that's why we have the isFontFaceSupported.ready() callback mechanism
I’m not terribly happy with this asynchronous delay, so I’ve written an alternative that uses browser userAgent sniffing. This practice is not recommended and is not terribly future-proof, but it’s the only synchronous solution available.
/*! * isFontFaceSupported - Sniff variant - v0.9 - 12/19/2009 * http://paulirish.com/2009/font-face-feature-detection/ * * Copyright (c) 2009 Paul Irish * MIT license *//* Browser sniffing is bad. You should use feature detection. Sadly the only feature detect for @font-face is asynchronous. So for those that *need* a synchronous solution, here is a sniff-based result:*/varisFontFaceSupported=function(){varua=navigator.userAgent,parsed;if(/*@cc_on@if(@_jscript_version>=5)!@end@*/0)returntrue;if(parsed=ua.match(/Chrome\/(\d+\.\d+\.\d+\.\d+)/))returnparsed[1]>='4.0.249.4';if((parsed=ua.match(/Safari\/(\d+\.\d+)/))&&!/iPhone/.test(ua))returnparsed[1]>='525.13';if(/Opera/.test({}.toString.call(window.opera)))returnopera.version()>='10.00';if(parsed=ua.match(/rv:(\d+\.\d+\.\d+)[^b].*Gecko\//))returnparsed[1]>='1.9.1';returnfalse;}
2009.09.24: updated the code, and threw it all on github. callback style makes its debut here. This code matches the exact same implementation that’s in Modernizr 1.0
2009.12.18: Added a useragent sniffing alternative for those who want reliable synchronous detection.
2009.12.19: New, smaller font file (Thanks Ethan Dunham). The file is now 15% smaller. Script does not remove the extra element it adds to the DOM now, as to assure more accurate results.
2010.11.02: I now recommend a different technique.. It’s from Diego Perini.. The code is below:
Back in the May issue of .net magazine, my friend and talented designer Tom Kershaw wrote an article on improving your speed, efficiency and polish with Photoshop. [PDF download here]
I wrote a sidebar about better ways for front-end developers to work with visual designers. Funnily enough, because of the lead time, by the time the magazine got to press, some of my recommendations (e.g. “embrace sIFR”) were already out of date.
If anyone has additional recommendations on how front-end developers and designers can improve their workflow, I’d love to see them in the comments.
The versioning system they devised is a very clever way to always serve you the most up-to-date script. For example, you request version 2 of SWFObject and it’ll currently deliver 2.2, but in the future you’ll always get the most recent 2.x release.
I did a bit of research on how they handle these cases, and since these facts aren’t elsewhere, they needed a home. :)
The current caching rules are as follows:
Request
Response cached for
/1.3.2/jquery.min.js
one year
/1.3/jquery.min.js
no caching
/1/jquery.min.js
one hour
jQuery URLs used only for illustration purposes. It’s the same case with all scripts’ minor/major versions
Minified and unminified files are treated the same
Previous to now, using the google.load('jquery','1.2.6') technique was the only way to ensure the script stayed considerably cached. Direct path-based access scripts were only cached for one day. But no worries, because all is better now.