May 28, 2009

CSS, Sprites and arcane winged demons

Maybe it's my fault, maybe not. The truth is that I had some problems optimizing my webserver load with CSS Sprites, a nice, clean technique to max the efficiency of every image. Let's go into details:

I used a set of flag icons (taken from Fam fam fam) in a site admin zone. There are 247 flags, sweet and small (16x11) and that's all I needed. In this admin you can browse through tables with tons of data, to the point that all of the flags may be shown at once. More than once, in fact. So in a clean-cached browser you probably end requesting 247 images from the server. Even if the only answer were a 304 Not modified (in another scenario) they're too many requests. No way, being the "optimizer" I am, I need to change that.

Stacking the icons

I created a script to automate the process of loading all 247 images, pack them into a bigger one and building the CSS. The PHP is too hard-coded to put as an example, but maybe one day I can generalize it as to be reusable. The thing is it fit my needs in that moment and the result was this:

  • The Flags Sprite

And a CSS that look like this:

.flag {
  background-image:url(/img/flags.png);
  display:block;
  text-decoration:none;
  height:11px;
  width:16px;
}
.flag-ad { background-position:-16px 0; }
.flag-ae { background-position:-32px 0; }
...
.flag-zm { background-position:-176px -198px; }
.flag-zw { background-position:-192px -198px; }

And finally I made some changes to the admin, so the HTML output now is something like this:

<table>
  <tbody>
    <tr>
      ...
      <td><a href="#" class="flag flag-es" title="Spain"></a></td>
      ...
    </tr>
    <tr>
      ...
      <td><a href="#" class="flag flag-us" title="United States"></a></td>
      ...
    </tr>
    <tr>
      ...
      <td><a href="#" class="flag flag-de" title="Germany"></a></td>
      ...
    </tr>
  </tbody>
</table>

The curse

It worked like a charm. Well, almost in any browser you can name. But, damn, don't talk me about IE, because those aren't browsers! IE shook me once again by not behaving as I expected, that is, like a W3C compliant browser. But that's no surprise, is it?

  • CSS Sprites in W3C browsers

  • CSS Sprites in IE 6

I thought it could be a problem with different default CSS values, but I have used a CSS reset technique at the top of the site, so it couldn't be, right?

Right, it wasn't the cause. I tested with some others reset stylesheets from the net, even the demonized * { margin:0; padding:0; } but none worked. The bug was in some other place, so I began trying, thinking and having some luck finally. And when I say "finally" it really means "after some hours boiling my brains".

Size matters

I remembered the line-height property. Maybe it can shed some light on my troubles, but it was a waste of time. But wait, if IE6 is making weird things? Sure it was, but I mean, maybe is it showing something that's not there? A little addition to the CSS made me see the light:

.flag { font-size:0; }

And that's all! IE6 filled the empty link tag with a &nbsp; and then show it. As the default font size is 18px height, it showed 7px more than it should. Almost half the height of a flag! In the end, size matters, even more with things that shouldn't be there, but are.

End of story

As a little regard

The code below shows the final CSS with all 247 flag positions within the sprite, using their corresponding ISO 3166-1 alpha-2 codes. I hope it helps somebody. See ya!

.flag {
  background-image:url(img/flags.png);
  display:block;
  font-size:0;
  height:11px;
  width:16px;
}
.flag-ad{background-position:-16px 0;}
.flag-ae{background-position:-32px 0;}
.flag-af{background-position:-48px 0;}
.flag-ag{background-position:-64px 0;}
.flag-ai{background-position:-80px 0;}
.flag-al{background-position:-96px 0;}
.flag-am{background-position:-112px 0;}
.flag-an{background-position:-128px 0;}
.flag-ao{background-position:-144px 0;}
.flag-aq{background-position:-160px 0;}
.flag-ar{background-position:-176px 0;}
.flag-as{background-position:-192px 0;}
.flag-at{background-position:0 -11px;}
.flag-au{background-position:-16px -11px;}
.flag-aw{background-position:-32px -11px;}
.flag-ax{background-position:-48px -11px;}
.flag-az{background-position:-64px -11px;}
.flag-ba{background-position:-80px -11px;}
.flag-bb{background-position:-96px -11px;}
.flag-bd{background-position:-112px -11px;}
.flag-be{background-position:-128px -11px;}
.flag-bf{background-position:-144px -11px;}
.flag-bg{background-position:-160px -11px;}
.flag-bh{background-position:-176px -11px;}
.flag-bi{background-position:-192px -11px;}
.flag-bj{background-position:0 -22px;}
.flag-bl{background-position:-16px -22px;}
.flag-bm{background-position:-32px -22px;}
.flag-bn{background-position:-48px -22px;}
.flag-bo{background-position:-64px -22px;}
.flag-br{background-position:-80px -22px;}
.flag-bs{background-position:-96px -22px;}
.flag-bt{background-position:-112px -22px;}
.flag-bv{background-position:-128px -22px;}
.flag-bw{background-position:-144px -22px;}
.flag-by{background-position:-160px -22px;}
.flag-bz{background-position:-176px -22px;}
.flag-ca{background-position:-192px -22px;}
.flag-cc{background-position:0 -33px;}
.flag-cd{background-position:-16px -33px;}
.flag-cf{background-position:-32px -33px;}
.flag-cg{background-position:-48px -33px;}
.flag-ch{background-position:-64px -33px;}
.flag-ci{background-position:-80px -33px;}
.flag-ck{background-position:-96px -33px;}
.flag-cl{background-position:-112px -33px;}
.flag-cm{background-position:-128px -33px;}
.flag-cn{background-position:-144px -33px;}
.flag-co{background-position:-160px -33px;}
.flag-cr{background-position:-176px -33px;}
.flag-cu{background-position:-192px -33px;}
.flag-cv{background-position:0 -44px;}
.flag-cx{background-position:-16px -44px;}
.flag-cy{background-position:-32px -44px;}
.flag-cz{background-position:-48px -44px;}
.flag-de{background-position:-64px -44px;}
.flag-dj{background-position:-80px -44px;}
.flag-dk{background-position:-96px -44px;}
.flag-dm{background-position:-112px -44px;}
.flag-do{background-position:-128px -44px;}
.flag-dz{background-position:-144px -44px;}
.flag-ec{background-position:-160px -44px;}
.flag-ee{background-position:-176px -44px;}
.flag-eg{background-position:-192px -44px;}
.flag-eh{background-position:0 -55px;}
.flag-er{background-position:-16px -55px;}
.flag-es{background-position:-32px -55px;}
.flag-et{background-position:-48px -55px;}
.flag-fi{background-position:-64px -55px;}
.flag-fj{background-position:-80px -55px;}
.flag-fk{background-position:-96px -55px;}
.flag-fm{background-position:-112px -55px;}
.flag-fo{background-position:-128px -55px;}
.flag-fr{background-position:-144px -55px;}
.flag-ga{background-position:-160px -55px;}
.flag-gb{background-position:-176px -55px;}
.flag-gd{background-position:-192px -55px;}
.flag-ge{background-position:0 -66px;}
.flag-gf{background-position:-16px -66px;}
.flag-gg{background-position:-32px -66px;}
.flag-gh{background-position:-48px -66px;}
.flag-gi{background-position:-64px -66px;}
.flag-gl{background-position:-80px -66px;}
.flag-gm{background-position:-96px -66px;}
.flag-gn{background-position:-112px -66px;}
.flag-gp{background-position:-128px -66px;}
.flag-gq{background-position:-144px -66px;}
.flag-gr{background-position:-160px -66px;}
.flag-gs{background-position:-176px -66px;}
.flag-gt{background-position:-192px -66px;}
.flag-gu{background-position:0 -77px;}
.flag-gw{background-position:-16px -77px;}
.flag-gy{background-position:-32px -77px;}
.flag-hk{background-position:-48px -77px;}
.flag-hm{background-position:-64px -77px;}
.flag-hn{background-position:-80px -77px;}
.flag-hr{background-position:-96px -77px;}
.flag-ht{background-position:-112px -77px;}
.flag-hu{background-position:-128px -77px;}
.flag-id{background-position:-144px -77px;}
.flag-ie{background-position:-160px -77px;}
.flag-il{background-position:-176px -77px;}
.flag-im{background-position:-192px -77px;}
.flag-in{background-position:0 -88px;}
.flag-io{background-position:-16px -88px;}
.flag-iq{background-position:-32px -88px;}
.flag-ir{background-position:-48px -88px;}
.flag-is{background-position:-64px -88px;}
.flag-it{background-position:-80px -88px;}
.flag-je{background-position:-96px -88px;}
.flag-jm{background-position:-112px -88px;}
.flag-jo{background-position:-128px -88px;}
.flag-jp{background-position:-144px -88px;}
.flag-ke{background-position:-160px -88px;}
.flag-kg{background-position:-176px -88px;}
.flag-kh{background-position:-192px -88px;}
.flag-ki{background-position:0 -99px;}
.flag-km{background-position:-16px -99px;}
.flag-kn{background-position:-32px -99px;}
.flag-kp{background-position:-48px -99px;}
.flag-kr{background-position:-64px -99px;}
.flag-kw{background-position:-80px -99px;}
.flag-ky{background-position:-96px -99px;}
.flag-kz{background-position:-112px -99px;}
.flag-la{background-position:-128px -99px;}
.flag-lb{background-position:-144px -99px;}
.flag-lc{background-position:-160px -99px;}
.flag-li{background-position:-176px -99px;}
.flag-lk{background-position:-192px -99px;}
.flag-lr{background-position:0 -110px;}
.flag-ls{background-position:-16px -110px;}
.flag-lt{background-position:-32px -110px;}
.flag-lu{background-position:-48px -110px;}
.flag-lv{background-position:-64px -110px;}
.flag-ly{background-position:-80px -110px;}
.flag-ma{background-position:-96px -110px;}
.flag-mc{background-position:-112px -110px;}
.flag-md{background-position:-128px -110px;}
.flag-me{background-position:-144px -110px;}
.flag-mf{background-position:-160px -110px;}
.flag-mg{background-position:-176px -110px;}
.flag-mh{background-position:-192px -110px;}
.flag-mk{background-position:0 -121px;}
.flag-ml{background-position:-16px -121px;}
.flag-mm{background-position:-32px -121px;}
.flag-mn{background-position:-48px -121px;}
.flag-mo{background-position:-64px -121px;}
.flag-mp{background-position:-80px -121px;}
.flag-mq{background-position:-96px -121px;}
.flag-mr{background-position:-112px -121px;}
.flag-ms{background-position:-128px -121px;}
.flag-mt{background-position:-144px -121px;}
.flag-mu{background-position:-160px -121px;}
.flag-mv{background-position:-176px -121px;}
.flag-mw{background-position:-192px -121px;}
.flag-mx{background-position:0 -132px;}
.flag-my{background-position:-16px -132px;}
.flag-mz{background-position:-32px -132px;}
.flag-na{background-position:-48px -132px;}
.flag-nc{background-position:-64px -132px;}
.flag-ne{background-position:-80px -132px;}
.flag-nf{background-position:-96px -132px;}
.flag-ng{background-position:-112px -132px;}
.flag-ni{background-position:-128px -132px;}
.flag-nl{background-position:-144px -132px;}
.flag-no{background-position:-160px -132px;}
.flag-np{background-position:-176px -132px;}
.flag-nr{background-position:-192px -132px;}
.flag-nu{background-position:0 -143px;}
.flag-nz{background-position:-16px -143px;}
.flag-om{background-position:-32px -143px;}
.flag-pa{background-position:-48px -143px;}
.flag-pe{background-position:-64px -143px;}
.flag-pf{background-position:-80px -143px;}
.flag-pg{background-position:-96px -143px;}
.flag-ph{background-position:-112px -143px;}
.flag-pk{background-position:-128px -143px;}
.flag-pl{background-position:-144px -143px;}
.flag-pm{background-position:-160px -143px;}
.flag-pn{background-position:-176px -143px;}
.flag-pr{background-position:-192px -143px;}
.flag-ps{background-position:0 -154px;}
.flag-pt{background-position:-16px -154px;}
.flag-pw{background-position:-32px -154px;}
.flag-py{background-position:-48px -154px;}
.flag-qa{background-position:-64px -154px;}
.flag-re{background-position:-80px -154px;}
.flag-ro{background-position:-96px -154px;}
.flag-rs{background-position:-112px -154px;}
.flag-ru{background-position:-128px -154px;}
.flag-rw{background-position:-144px -154px;}
.flag-sa{background-position:-160px -154px;}
.flag-sb{background-position:-176px -154px;}
.flag-sc{background-position:-192px -154px;}
.flag-sd{background-position:0 -165px;}
.flag-se{background-position:-16px -165px;}
.flag-sg{background-position:-32px -165px;}
.flag-sh{background-position:-48px -165px;}
.flag-si{background-position:-64px -165px;}
.flag-sj{background-position:-80px -165px;}
.flag-sk{background-position:-96px -165px;}
.flag-sl{background-position:-112px -165px;}
.flag-sm{background-position:-128px -165px;}
.flag-sn{background-position:-144px -165px;}
.flag-so{background-position:-160px -165px;}
.flag-sr{background-position:-176px -165px;}
.flag-st{background-position:-192px -165px;}
.flag-sv{background-position:0 -176px;}
.flag-sy{background-position:-16px -176px;}
.flag-sz{background-position:-32px -176px;}
.flag-tc{background-position:-48px -176px;}
.flag-td{background-position:-64px -176px;}
.flag-tf{background-position:-80px -176px;}
.flag-tg{background-position:-96px -176px;}
.flag-th{background-position:-112px -176px;}
.flag-tj{background-position:-128px -176px;}
.flag-tk{background-position:-144px -176px;}
.flag-tl{background-position:-160px -176px;}
.flag-tm{background-position:-176px -176px;}
.flag-tn{background-position:-192px -176px;}
.flag-to{background-position:0 -187px;}
.flag-tr{background-position:-16px -187px;}
.flag-tt{background-position:-32px -187px;}
.flag-tv{background-position:-48px -187px;}
.flag-tw{background-position:-64px -187px;}
.flag-tz{background-position:-80px -187px;}
.flag-ua{background-position:-96px -187px;}
.flag-ug{background-position:-112px -187px;}
.flag-um{background-position:-128px -187px;}
.flag-us{background-position:-144px -187px;}
.flag-uy{background-position:-160px -187px;}
.flag-uz{background-position:-176px -187px;}
.flag-va{background-position:-192px -187px;}
.flag-vc{background-position:0 -198px;}
.flag-ve{background-position:-16px -198px;}
.flag-vg{background-position:-32px -198px;}
.flag-vi{background-position:-48px -198px;}
.flag-vn{background-position:-64px -198px;}
.flag-vu{background-position:-80px -198px;}
.flag-wf{background-position:-96px -198px;}
.flag-ws{background-position:-112px -198px;}
.flag-ye{background-position:-128px -198px;}
.flag-yt{background-position:-144px -198px;}
.flag-za{background-position:-160px -198px;}
.flag-zm{background-position:-176px -198px;}
.flag-zw{background-position:-192px -198px;}