Paul Irish

Making the www great

jQuery Conference 2009

Currently rounding up the last day of #jqcon and it’s one of the best events I’ve been too. Crazy to see jQuery’s annual meetup double in size each year and scale with talent and organization just as nicely. I somehow tricked the attendees into voting two talk proposals up (thanks!), so I got to discuss two things this weekend.

I’d love to any feedback you may have and plan to write on these topics in more detail, but for now.. my slides:

jQuery Anti-Patterns for Performance & Compression

Employing Custom Fonts… Now!

Bulletproof @font-face Syntax

Let me introduce you to the best way to do your @font-face definitions:

1
2
3
4
@font-face {
  font-family: 'Graublau Web';
  src: url('GraublauWeb.eot?') format('eot'), url('GraublauWeb.woff') format('woff'), url('GraublauWeb.ttf') format('truetype');
}

This is the Fontspring @font-face syntax. I’ll circle back to why this is the best possible solution but let’s first review the other techniques’ weaknesses. Of course, the problem at the center of this is that IE needs an .eot font, and the other browsers must take a .ttf or .otf.

May 12th, 2010. If you’re looking to just put @font-face to use today, just head to FontSquirrel’s generator. It is an indispensible tool when implementing @font-face. If you want to know more about some of the why’s, carry on…

Okay, let’s see what we got here…

Conditional comments (via)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--
@font-face{
  font-family:'Graublau Web';
  src: url('GraublauWeb.otf') format('opentype');
}
-->

<!--[if IE]>
<mce:style type="text/css" media="screen"><!
@font-face{
  font-family:'Graublau Web';
  src: url('GraublauWeb.eot');
}
-->

Ugh. Seriously? We’d have to drop that in every html file or have unique iefonts.css files. No fun. Also, ugly.

Double declarations (via)

1
2
3
4
5
6
7
8
@font-face{
  font-family:'Graublau Web';
  src: url('GraublauWeb.eot'); /* here you go, IE */
}
@font-face{
  font-family:'Graublau Web';
  src: url('GraublauWeb.otf'); /* everyone else take this */
}

The problem here is that, as Andrea points out, IE will actually download the .otf file. We can’t have extra HTTP connections, so this is typically the solution:

1
2
3
4
@font-face {
  font-family: 'Graublau Web';
  src url('GraublauWeb.otf') format('opentype'); /* IE no comprende format()! */
}

Because after all, IE doesn’t understand the format hint, right? It’s true. But what really happens is that IE does a request for this filename:

GraublauWeb.otf’)%20format(‘opentype

Oops, looks like someone forgot a ? in their regular expression! But hey, a 404 is a lot better than grabbing a file that’s 20-100k. Let’s kill that 404:

Mo’ Bulletproofer (via)

1
2
3
4
5
6
7
8
@font-face{
  font-family:'Graublau Web';
  src: url('GraublauWeb.eot'); /* here you go, IE */
}
@font-face{
  font-family:'Graublau Web';
  src: url(//:) format ('no404'), url('GraublauWeb.otf') format('opentype'); /* tricky! */
}

Richard Fink proposed this alternate syntax actually as a response to this post, but I’ve included it back here. The trick is to use url(//:), to prevent IE from 404’ing on the ttf/otf file. In his article he lists a few reasons why he prefers the semantics of this alternative. I understand the argument, but I don’t like repeating myself, so I’m gonna keep trucking:

The local reference

1
2
3
4
5
@font-face {
  font-family: 'Graublau Web';
  src: url(GraublauWeb.eot);
  src: local('Graublau Web Regular'), url(GraublauWeb.otf) format('opentype');
}

Much more concise and clean. Here, non-IE browsers skip any .eot file and move on. IE will try to parse the second src value, but it can’t understand the local() location nor the multiple locations, so it resorts to the EOT instead. Worth noting that IE will always dive to the last src:url() value to start, so this won’t work.

1
2
src: url(GraublauWeb.eot);
src: url(GraublauWeb.otf); /* Yeah IE will only try this one. :( */

The other benefit.. if it just so happens that a user actually has your custom font installed, this’ll save them the download. The one catch is that Safari on OS X will use only the Postscript name instead of the full font name; so when they differ, include both names:

Bulletproof @font-face

1
2
3
4
5
6
@font-face {
  font-family: 'Graublau Web';
  src: url('GraublauWeb.eot');
  src: local('Graublau Web Regular'), local('Graublau Web'),
         url('GraublauWeb.otf') format('opentype');
}

Bulletproof @font-face: Smiley variation

1
2
3
4
5
6
@font-face {
  font-family: 'Graublau Web';
  src: url('GraublauWeb.eot');
  src: local('☺︎'),
         url('GraublauWeb.otf') format('opentype');
}

Added 2010.02.04: There has been concern over specifying local font names. The primary reason is that you cede control to the user’s machine, potentially showing a locally installed font instead of the one you want to serve. While that will load faster, there’s a very small chance the file could be wrong.

To account for this gotcha, I’ve specified a local font name of ☺︎. Yes, it’s a smiley face. The OpenType spec indicates any two-byte unicode characters won’t work in a font name on Mac at all, so that lessens the likelihood that someone actually released a font with such a name. This technique is recommended if you think a locally installed version of this font is not in your best interest. Added 2010.05.05: The smiley variation is my new recommendation. ← Click through to read why.

The Fontspring @font-face syntax

Ethan (the same guy behind Font Squirrel), figured out a trick to make it work everywhere (including IE6, Android, iOS) including all the bugs the above tricks work around.. It’s called the Fontspring @font-face syntax. It goes a little something like this:

1
2
3
4
@font-face {
  font-family: 'Graublau Web';
  src: url('GraublauWeb.eot?') format('eot'), url('GraublauWeb.woff') format('woff'), url('GraublauWeb.ttf') format('truetype');
}

Remember that 404 problem from above? The question mark solves that. That’s about it.

Also worth noting, the correct format type for an .eot is ‘embedded-opentype’. But if you change it to anything invalid, IE9 will prioritize the WOFF file above the EOT, which is good! So we use ‘eot’.. but this could just as easily be ‘ie9-give-me-the-woff-instead’.

Demo

I’ve added a test page with a few syntax variants here for crossbrowser testing

Additional notes and gotchas:

  • Including a font-variant property inside the definition will cause it to not work in Safari (4.0.3 tested) as well as IE 6-8. (Thanks Sid)
  • Including a font-style property the definition is safe and the definition will still succeed with all browsers, however IE ignore what you define it as.But if you want a bold or italic style of a font to display, you’ll need to make two definitions. Read the It Takes Two, Baby section on Nice Web Type’s how to use css font face article
  • Opera will fail if you do not use quotes (single or double) in your local() font name. So: local('Use Quotes'). I’ve filed this bug with Opera as it’s a spec violation. Thank you to Scott Kimler and Richard Fink for helping with the tireless research on Opera’s quirks.
  • Safari permission error? Some people experience a dialog asking for permission to use a local font. This only happens in webkit. [screenshot]
    • Thibault Bardat-Bujoli indicated that this behavior is due to Linotype FontExplorer X. For any font added but not ‘activated’, it will intercept requests to use it. You can disable this behavior within the FontExplorer UI [jpg]. I have heard no reports of this behavior on machines lacking FontExplorer
    • More on my @font-face gotchas post
  • Chrome? A few rumors are going around that a Chrome beta or dev build has @font-face support. There is no install of Chrome that has @font-face enabled by default [actually, it’s in dev builds now!], however you can enable it in Chrome 3 with an executable switch. I’m keeping this page on @font-face and Google CHrome up to date with all the details.
  • You can feed FF, Opera and Safari a truetype but specify it as format(‘opentype’) and it’s just fine. So for all intents and purposes, if your font is opentype or truetype, the format hint is optional.
  • The font name that specified in your @font-face declaration cannot exceed 31 characters in length. IE will fail on anything larger.
  • I first found this bulletproof technique in use by Mark Pilgrim at http://diveintohtml5.org. My hat is off to you, good sir.

SVG? WOFF?

Since Google Chrome won’t have typical @font-face support until version 4, we can snag it early by serving it SVG fonts. WOFF is a new format that is officially supported in Firefox 3.6 Can we integrate those into this syntax? Definitely.

1
2
3
4
5
6
7
8
@font-face {
  font-family: 'Graublau Web';
  src: url('GraublauWeb.eot');
  src: local('☺︎'),
    url("GraublauWeb.woff") format("woff"),
    url("GraublauWeb.otf") format("opentype"),
    url("GraublauWeb.svg#grablau") format("svg");
}

The order of those is deliberate and discussed in the comments here. Hat tip to Snook for being the first to drag SVG into the party. Font Squirrel and Nice Web Type have also been very thoughtful in their work.

Android :/

Bulletproof smiley doesn’t work in Android 2.2-2.3 (which supports @font-face, but not the local() definition). Personally, I’m okay with that because I don’t want to wait another 5 seconds for 100kb of fonts to load before I actually see text (thanks webkit FOUT bug).

But hey, some people might prefer webfonts over quick loads.. and for you, there is a pretty okay fix to that:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@font-face {
  font-family: 'Graublau Web';
  src: url('GraublauWeb.eot');
  src: local('☺︎'),
    url("GraublauWeb.woff") format("woff"),
    url("GraublauWeb.otf") format("opentype"),
    url("GraublauWeb.svg#grablau") format("svg");
  }
@media screen and (max-device-width: 480px) {
  @font-face {
    font-family: "Graublau Web";
    src: url("GraublauWeb.woff") format("woff"),
    url("GraublauWeb.otf") format("opentype"),
    url("GraublauWeb.svg#grablau") format("svg");
}}

For mobile we redeclare the @font-face declaration without the local() guy. We’ll also give it the woff, opentype and svg, even though no mobile devices support WOFF so far. Also for this media query to succeed in Android Webkit, you’re gonna want a viewport meta tag.

1
 <meta name="viewport" content="width=device-width, initial-scale=1.0">

That’ll do. :)

I’m confused and lazy. Help?

Want to absorb the benefits of this article instantly? Use Font Squirrel’s awesome @font-face generator. It does all of this for you, and more. If you’re less lazy, read through Nice Web Type’s How To for all the deets.

2009.09.10: Esta entrada del blog en español: Sintaxis de @font-face | CSSBlog ES

2009.09.11: Una buona panoramica e questa tecnica in italiano: font-face e Webfonts: come usarli

2009.09.16: I’ve updated the article with the Mo’ Bulletproofer syntax as well as some more detailed gotcha’s and notes.

2009.09.18: This blog article has been translated to Japanese

2009.09.22: Added details around the Safari permission error, which Thibault Bardat-Bujoli tracked down to the Linotype FontExplorer X app.

2009.11.08: New sections for SVG/WOFF and the Font Squirrel generator. Link to Nice Web Type’s How To article for doing italic along with normal.

2009.11.17: Note on font name length from FontSquirrel.

2009.12.02: Update on Chrome support with link to Chrome and @font-face: it’s here!

2010.02.04: Added the smiley variation.

2010.05.05: Changed default recommendation to the smiley variation. A while bunch of new things to consider: @font-face gotchas.

2011.02.01: Added the android bit.

2011.02.03: Updated with fontspring syntax! Go Ethan!
Take a look at some of my other (recently updated) webfont stuff: