Archive for the 'CSS' Category

Data URIs, MHTML and IE7/Win7/Vista blues

Monday, December 7th, 2009

2010 update:
Lo, the Web Performance Advent Calendar hath moved

Dec 7 This is the seventh in the series of performance articles as part of my 2009 performance advent calendar experiment. Stay tuned for the next articles.

UPDATE: While this post is an interesting study, the problem it solves turns out to be much simpler. The details are here. In resume: you need a closing separator and it all works fine in IE7/Vista/Win7

Let's start this post as a dialog:

- Data URIs are a way to embed the base64-encoded content of images inside HTML and CSS. Inlining images in markup or stylesheets helps you save precious HTTP requests. It's an alternative to CSS sprites.
- BUT! IE6 and IE7 don't support data URIs
- Yes, for them you can inline the images using MHTML
- BUT! Looks like IE7 on Vista and IE7 on Win7 have problems with MHTML
- [Sigh...] For those browser/OS combos there's a solution too.

I'll quickly go over what are data URIs and how MHTML addresses IE6 and IE7. If you're familiar with these, feel free to skip down to the Vista blues part.

Data URIs

Here's how to use data URIs in your pages:

  1. take an image:
    horoscopes icon image
  2. Use PHP to read this image's binary content and encode it using base64 encoding:
    $ php -r "echo base64_encode(file_get_contents('horoscopes.png'));"
    iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAADAFBMVEX///8mPnru9...
    ...more scary stuff goes here...
    dajNGGzlDAAAAABJRU5ErkJggg==
  3. To use this image in CSS, paste the encoded image content in the stylesheet following this syntax:
    .horoscopes {
        background-image: url("data:image/png;base64,iVBOR...rkJggg==");
    }

    Note: the trailing == is part of the image content it's not part of the syntax

  4. Alternatively if you want to use this image in the HTML, you can go like:
    <img src="data:image/png;base64,iVBOR...rkJggg==" />

And that's about it for data URIs, it's quite simple actually.

Data URIs in the wild

To see real-life, high-traffic sites using data URIs look no further than your favorite search engines.

Yahoo! Search using data URI in CSS for a button background:

yahoo search screenshot

Google Search using data URI in an IMG tag for a video thumb:

Google search screenshot

Base64-encoded filesizes

The base64 encoding adds about 33% to the filesize. But, then you gzip the result, the compression brings the size back to the original. Plus or minus. Interestingly enough, sometimes after base64 and gzip, the result is smaller than the original. But don't count on that.

Theoretically also when you base64 encode a bunch of images and inline them in the same CSS or HTML, you'll have a larger string to compress, so increasing the chance of repetitions and compressing better. (Todo: test this assumption)

In any event, the benefit of reducing HTTP requests will likely greatly outweigh any fluctuation in the file size.

MHTML

IE8 supports data URIs, but earlier IEs do not. For them you can use MHTML (Multipart HTML, blogged previously here)

Continuing with the previous example, in order to display the horoscopes image in IE < 8 you can use a stylesheet like this:

/*
Content-Type: multipart/related; boundary="_MY_BOUNDARY_SEPARATOR"

--_MY_BOUNDARY_SEPARATOR
Content-Location:horoscopes
Content-Transfer-Encoding:base64

iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAADAFBMVEX///8mPnru9....U5ErkJggg==
*/

.horoscopes{*background-image:url(mhtml:http://example.org/styles.css!horoscopes);}

A few notes on this example:

  • the image content is base64-encoded just like before
  • if you need more images, use your chosen separator prepended with --, in this case --_MY_BOUNDARY_SEPARATOR
  • Content-Location:horoscopes is how you give a name to the image in order to use it later
  • then later in your stylesheet you can reference the name in the stylesheet using http://url/styles.css!horoscopes
  • you need absolute URLs in the mhtml:http://.... part (that's retarded, hope I'm mistaken. Didn't work for me with relative URLs)
  • you can use a single CSS file to support both old IE as well as new browsers, but you'll have to repeat the image stream twice, probably a better approach is to use browser specific stylesheets

For more examples of MHTML (which is also used for multipart email messages) check this and for a brilliant example of using one MHTML to embed images in HTML (not CSS), check Hedger's test page here (appears 404 at the moment of writing). For a tool to automate the dataURI-zation/MHTML-ization, be sure to check Nicholas Zakas' CSSEmbed

The problem with IE7 on Vista (and Win7)

So far we have data URIs and MHTML fallback. Turns out we're not done yet, because IE7 on Vista and Windows 7 fails with the MHTML example. Two points:

  • IE8 is the default in Windows 7, but it comes with several IE7 modes (either by default or turned on by users when their favorite pages break). Compatibility view, ie7 mode, ie8 in ie7 mode, blah-blah, it's all pretty confusing, but all modes with the exception of IE8 in proper IE8 standards don't work with the MHTML
  • I tested Windows 7 evaluation copy, but I have the bad, bad feeling that Windows 7 normal copy will be as broken when it comes to MHTML. I believe it has something to do with security, if anyone finds an article on MSDN, or anywhere, please share.

The solution to the Vista problem is to split the CSS shown above (the one that uses MHTML) in two: one which contains the encoded images (the commented part) and a second one which only has the normal CSS selectors part.

In other words have something like:

  1. mhtml.txt:
    Content-Type: multipart/related; boundary="_MY_BOUNDARY_SEPARATOR"
    
    --_MY_BOUNDARY_SEPARATOR
    Content-Location:horoscopes
    Content-Transfer-Encoding:base64
    
    iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAADAFBMVEX///8mPnru9....U5ErkJggg==
    

    No need to use comments. Could be .txt, .css or anything you like. In my tests the content-type didn't matter - text/html, text/css, text/plain all worked

  2. styles.css:
    .horoscopes{*background-image:url(mhtml:http://example.org/mhtml.txt!horoscopes);}

    The normal CSS simply references the new MHTML file appending !identifier

  3. in your HTML you don't need to reference the MHTML document, you just point your link tag to styles.css as usual and styles.css contains the reference to the MHTML doc

Time to celebrate? Almost.

Another ugly bug surfaces - this procedure outlined above works only once. Once the MHTML.txt is cached, it stops working. That means repeating visits to the page or other pages using the same mhtml. The effect also means hovering on the same page. Say you have two images - one normal and one mouse over, both in the same MHTML. The normal works and the hover breaks, mouseout breaks too. Insane, isn't it?

There's a solution though - you should make the browser request the MHTML document every time. This is kind of backwards when it comes to performance optimization, where we want to cache everything. It's pretty unfortunate. The best you can do is avoid sending the MHTML every time, but only send a "not-modified" header. In order for this to work, the browser has to send If-Modified-Since or If-None-Match.

So you can send your MHTML file with an ETag (yes, ETags can help sometimes ;) ) Then the browser will send an If-None-Match and you can happily reply "304 Not Modified".

Sounds complicated? It sure is. And, remember, all this is only for IE7 (or any IE7 mode) on Vista and Windows 7. All other IEs on all other platforms work with the easy MHTML and IE8 works with data URIs.

Everything is a trade-off (see last night's post) and I can understand how you may think "That is one big tradeoff". There's always the option of serving normal images to the affected browser/os and just don't implement this optimization for them.

But it's good to know there is a solution.

DataSprites class

Let me offer this DataSprites PHP class I coded that takes care of all the scenarios. It takes a bunch of image filenames as input and produces:

  • a CSS containing data URIs for normal browsers (example output)
  • an MTHML CSS fallback for IE6,7 (example output)
  • a CSS that refers to an additional MHTML for IE7 on Vista, Win7 (example output - css and mhtml). The MHTML in this case is sent out with an ETag (derived from the input image filenames) and consecutive requests with the same ETag return 304 Not Modified

You can see a test page here and view the source code here. The directory listing is also available if you want to grab the images for testing.

The perfect use case for such an approach is when you want to combine background images on the fly. When you would normally use CSS sprites, but you want to produce them dynamically at run time (maybe because you have too many combinations?).

I've tested this in Safari, Firefox, Opera, IE6, IE7 and IE8 (in all compat modes) on Vista and Windows 7 evaluation copy. If you find the test page is not working for you, please let me know.

In this DataSprites class, I'm checking the problem OS looking for the strings "Windows NT 6" and "Windows NT 7" in the user agent string. My Win7 evaluation copy had "Windows NT 6.1" in the UA, so I may be a little forwards-aggressive with the check, but somehow I thought Win7 proper should have "Windows NT 7" in the UA string. And also that Win7 will suffer from the same issue as Vista and Windows 7 evaluation.

UPDATE: Added a hover example.

UPDATE 2: Recorded a screencast to demo the IE7/Vista experience

Thanks!

Thanks for reading! Now you know all about data URIs, MHTML, and maybe a little too much about IE7/Vista's challenges :) Ready to use data URIs and save some HTTP requests?

 

Reducing the number of page components

Saturday, December 5th, 2009

2010 update:
Lo, the Web Performance Advent Calendar hath moved

Dec 5 This is the fifth in the series of performance articles as part of my 2009 performance advent calendar experiment. Stay tuned for the next articles.

Let's talk a but about waterfall optimization - the first thing that happens in Mr.Page's life. The best way to optimize and speed up the waterfall is to have less stuff in it. The fewer page components, the faster the page - simple as that.

Fewer components vs. component weight

The size of the page components, meaning their size in kB, is important. It makes sense - smaller pages will load faster, 100K JavaScript will load faster than 150K. It's important to keep sizes low, but it should be clear that the number of components is even more important than their filesize.

Why? Because every HTTP request has overhead.

OK, but how bad can it be, someone might ask. If you look at an HTTP request - it has a header and a body. A 100K body will greatly outweigh the size of the headers, no matter how bloated they are.

Here's the headers for a request to Yahoo! Search:

Host: search.yahoo.com
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5;) Firefox/3.5.5
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive

To that request the server responds with the body (content) of the response prepended with some headers like:

HTTP/1.1 200 OK
Date: Sat, 05 Dec 2009 07:36:25 GMT
P3P: policyref="http://p3p.yahoo.com/w3c/p3p.xml", CP="CAO DSP COR CUR... blah, blah"
Set-Cookie: sSN=nTMt3Lo2...crazy stuff...nLvwVxUU; path=/;domain=.search.yahoo.com
Cache-Control: private
Connection: close
Content-Type: text/html; charset=ISO-8859-1

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">

<html lang="en"><head><meta...

This is 352 bytes request header and 495 bytes response header. Not so bad, eh? No matter how hard you try to make your cookies monster-size, the response body (9k gzipped in this case) will always be significantly bigger. So what's the problem with the overhead of the HTTP requests then?

The size of the headers is a problem when you make requests for small components - say requests for small icons - for example 1K or under. In this case you exchange 1K headers to get 1K of useful data to present to the user. Clearly a waste. Additionally this 1K of headers can grow once you start writing more cookies. It may very well happen that the HTTP headers size is bigger than the actual icon you need. And even if the headers are not bigger that the component, they are still big when you think percent-wise. 1K of 10K is 10%.

But the HTTP headers size is only one (and the smaller) of the problems.

The bigger problem is the HTTP connection overhead.

HTTP connection overhead

What happens (at a high level) when you type a URL and hit Enter? The browser sends a request to the server. Which server? The browser needs to know the IP address of the server, so if it doesn't have it in the cache, it makes a DNS lookup. Than the browser establishes a connection to the server. Then it waits for the first byte of the response from the server. Then it receives the full response (payload).

Here's how this looks like graphically represented by webpagetest.org

And the color legend:

  1. DNS lookup
  2. Initial connection
  3. TTFB (Time to first byte)
  4. Payload

So what do we have here - looks like in this particular case the browser is downloading content about 40% of the time. The rest of the time it's... well, not downloading content. How's that for an overhead. And the part of not downloading can be even greater, this above was just one example.

Now how about this - a bird-eye view of the 4 last pages in webpagetest.org's test history - just some random pages people have tested.

Do you see a lot of blue (time spent downloading content). Not as much as you would've hoped. There's some DNS lookups, some orange... and OMG, talk about going green! :)

In fact you may notice that the smaller the component, the smaller is the blue part.

What does all this tell us?

  1. A significant chunk of time is spent in activities other than downloading.
  2. Smaller components still incur HTTP overhead and for them the relative penalty (relative to their size) is atrocious.

So what's a performance optimizer to do? Reduce the number of components and so pay fewer penalties.

Just remove stuff

Truth is - a lot of stuff on the pages today is not needed. Features no one likes or uses clutter the page and make it heavier. Well, what can you do, the boss/client/marketing guy wants that feature there. What you can do is at least try. You can introduce some science into the marketing activities - measure how much a specific feature is used. Or if you already have the data - look at it. Decide what can a page go without.

It's going to be tough convincing people to remove stuff. After all you spend time developing it. Someone dreamt up that feature initially. Someone (not the users) loves it. People hate to let go. But still, it's worth to try.

Combine components

Now that the phase of convincing people to remove stuff is done, what's left needs to be combined. How do you combine components? Simple - all JavaScripts go into a single file, all CSS into a single file. All decoration images go into a sprite.

JavaScript example (from a page to remain anonymous)

Before:

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js"></script>
<script src="/javascripts/application.js?1258423604"></script>
<script src="/javascripts/ui/minified/jquery.ui.all.min.js?1258423604"></script>
<script src="/javascripts/ui-ext/ui.bgiframe.min.js?1258423604"></script>
<script src="/javascripts/ui-ext/ui.stars.pack.js?1258423604"></script>
<script src="/javascripts/ui-ext/ui.dimensions.js?1258423604"></script>
<script src="/javascripts/ext/jquery.form.min.js?1258423604"></script>

After:

<script src="/javascripts/all.js"></script>

Sizes, gzipped: 70029 bytes before, 65194 bytes after. Simply merging files and there's even 6.9% saving!
And the more important saving: 6 less HTTP requests

Repeat for CSS. Before:

/stylesheets/general.css?1258423604
/stylesheets/global.css
/stylesheets/ui.stars.css
/stylesheets/themes/enation/enation.all.css
/public/template/css/132/1245869225

After:

<link type="text/css" rel="stylesheet" href="/stylesheets/all.css" />

Sizes, gzipped: before 14781 bytes, after 13352 bytes, saving 9.6%.
But the bigger saving: 4 less HTTP requests.

If you wonder how come the sizes before and after are different since we merely concatenate the contents of the files, well, the savings come from gzip compression. When you have more characters in the file, there's more chance that some will repeat which means they will compress better. That's one. And then, the compression itself has an overhead which you incur once for the whole bundle of files, as opposed to for every file.

Now - let's make decoration images into sprites. Before:

... 15 image requests, 6.8K

After: (1 sprited image)

Result size: 1.4K, 7 times smaller!

Here the savings are so dramatic partially because the source files are GIFs and the result is a PNG8 but that's a whole other post.

So in conclusion: file concatenation is just awesome. You save both: bytes to download and, much more importantly, HTTP requests. Less of the green stuff in the waterfall!

x-type component concatenation

So far we combined .js with .js, css with css and images with images. How about a cross-component type concatenation?

You can inline images inside HTML and CSS (and why not JS if you want) using data URIs (another post coming).

And you can inline CSS and JS inside HTML too.

This means you can have your whole application inside of just one HTML file if you wish. Inside of the HTML you have inline styles, scripts and images.

Combining CSS with JS

Now how about mixing CSS and JS into one component. You can do that, and it's especially suitable for lazy-loaded, widget-type functionality.

Say you've loaded the page, then the user clicks some rarely used button. You haven't downloaded that content that is supposed to wow the user upon click of the button. So you send a request to grab it. The new content may come in the form of a JSON string. And what if the new content requires some stylesheet that was not part of the base page? You'll have to make another request to download that stylesheet too.

Or, you can download both content and styles in the same JSON response. You simply inline the style information as a string in the JSON. So:

1. uses clicks, you request feature.js which goes like:

{"content":"<p class=\"wow\">I'm a feature</p>", "style": "wow{font-size: 60px}"}

2. You process the JSON and shove the content into the page

var o = JSON.parse(xhr.responseText);
$('result').innerHTML = o.content;

3. You add the styles to the head:

var wow = document.createElement('style');
wow.type = "text/css";
if (wow.textContent) { // FF, Safari
    wow.textContent = o.style;
} else {
    wow.styleSheet.cssText = o.style; // FF, IE
}
document.documentElement.firstChild.appendChild(wow);

Nice and simple. Makes the features (that progressively enhance the page) atomic and self-contained.

More to reducing components?

For more and creative ways to reduce HTTP components you can take a look at MXHR and Comet

Another thing to check is the Keep-Alive setting on your server. Remember how there were 4 steps in the component download. When you request a second component you can have the connection open so that you don't need to re-establish it (skipping step 2). And since the DNS lookup was already made you get rid of step 1. Skipping 2 out of 4 is not bad at all.

Summary

Reducing the number of page of components is the top priority of any web performance optimization effort. HTTP requests are costly. In part because of the headers size overhead, but mostly because of the connection overhead. The browser spends a disturbing amount of time not downloading stuff, and we can't allow this!

 

Statsy – more data points for markup quality

Monday, November 30th, 2009

In the spirit of the content-to-markup ratio bookmarklet, here's another one that gives you some more data points to help you judge the quality of a page's markup and help answer the old question - where does all this page weight go.

Install the statsy bookmarklet

Drag this link to your bookmarks:

statsy

the results

Once you run the bookmarklet it alerts these stats points:

  • JS attributes (e.g. onclick) - this is the sum of all onclick, onmouseover and so on including the attribute names. So for example <a onclick="#"> is 11 characters (bytes) of JavaScript attributes code
  • CSS style attributes - the sum of all style="..."
  • Inline JS - the sum of all the contents of all script tags (excluding the tag itself)
  • Inline CSS - sum of all <style> tag contents
  • All innerHTML - this is document.documentElement.innerHTML.length, it should be close to the ungzipped size of a page, provided the page is not doing a lot of DOM manipulation
  • # DOM elements - the total number of elements on the page is counted simply using document.getElementsByTagName('*').length

Here's example output:
statsy bookmarklet output

The code

The code is here for your tweaking pleasure

Thanks!

Hope you'll find this bookmarklet useful when looking at a page as a companion to YSlow/PageSpeed.

What else should I add to this bookmarklet? # of font tags, # of table tags...?

 

Flipity flop – mmm.phpied.com

Thursday, November 19th, 2009

Randomly browsing something on the iPhone it occurred to me that people could prefix their mobile sites with "mmm" instead of "m" or "i", as in mmm.mysite.tld. It's longer, true, but I don't think it will take longer to type three m's instead of one. And it's funny - mmm, it's like www. Only... flipped.

So I tweeted about it, got some good reasons why not to use something like this and Lucas Smith (of YUI fame) joked that I could setup mmm to deliver the same as www, only flipped.

Now, English is not my first language, so I miss out on many expressions, nuances, humor and so on. And there, I took it verbatim. :D

mmm.phpied.com

Ladies, and gentlemen, announcing the flipped version of this blog - http://mmm.phpied.com

You should be able to browse the whole site on its head, maybe with the exception of "popular posts" and other places, where I've hardcoded www.

how-to

All the changes had to be in my wordpress template, of course.

CSS first. Developer extraordinaire Ryan Grove have already solved the problem of x-browser flipping, so that was easy. At the bottom of the stylesheet, I added a new declaration block:

.flip {
  -moz-transform: rotate(180deg);
  -webkit-transform: rotate(180deg);
  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2);
}

Now the only thing was to add the class name to the body whenever the domain is mmm. That took some digging around WordPress, but eventually I decided to short-circuit the function call that takes the home URL option. WordPress provides a hook for that. So I added this to the beginning of functions.php in my template.


function mmm() {
  if (substr($_SERVER['HTTP_HOST'], 0, 4) === 'mmm.') {
    return 'http://mmm.phpied.com';
  } else {
    return 'http://www.phpied.com';
  }
}
add_filter('pre_option_home', 'mmm');

Finally, the change to header.php - replacing the body tag with:

<?php
  if (substr($_SERVER['HTTP_HOST'], 0, 4) === 'mmm.') {
    echo '<body class="flip">';
  } else {
    echo '<body>';
  }
?>

And voila - mmm.phpied.com

 

@font-face gzipping – take II

Tuesday, October 20th, 2009

Since my previous post on @font-face and gzipping, Paul Irish has asked (and so has @KLTF) what about WOFF?

WOFF is a newer format with built in compression and ability to store meta data.

So I took this stnf2woff utility and converted all the TTF and OTF files from my previous tests to WOFF. Below are the results.

Basically, the tests confirm that WOFF is already compressed, it doesn't need to be gzipped by the server and woff file sizes is comparable to the compressed TTF or OTF.

The tables show the original font-file size and the percentage shaved off after gzipping, converting to WOFF and gzipping the WOFF.

TTF to WOFF

font original, bytes gzip saves, % woff saves, % woff gzipped saves, %
AllerDisplay.ttf 95,616 50.04% 51.26% 51.30%
Aller_Bd.ttf 128,368 53.35% 54.34% 54.62%
Aller_BdIt.ttf 123,556 52.30% 53.46% 53.49%
Aller_It.ttf 120,876 51.64% 52.46% 52.55%
Aller_Lt.ttf 132,780 54.24% 55.45% 55.75%
Aller_LtIt.ttf 122,296 53.11% 54.17% 54.26%
Aller_Rg.ttf 134,436 52.86% 53.57% 53.87%
FFF_Tusj.ttf 1,543,648 34.69% 34.69% 34.68%
MarketingScript.ttf 55,160 54.63% 54.86% 54.86%
Sansation_Bold.ttf 19,644 46.72% 46.59% 46.76%
Sansation_Light.ttf 19,568 46.72% 46.52% 46.68%
Sansation_Regular.ttf 19,480 47.78% 47.70% 47.86%
Ubuntu-Title.ttf 15,108 50.54% 49.19% 49.32%
UglyQua-Italic.ttf 184,300 43.64% 44.16% 44.41%
UglyQua.ttf 120,424 50.59% 50.95% 51.58%
Vera-Bold-Italic.ttf 63,208 39.25% 41.39% 42.30%
Vera-Bold.ttf 58,716 37.59% 39.74% 40.53%
Vera-Italic.ttf 63,684 39.34% 41.03% 41.45%
Vera.ttf 65,932 39.07% 41.70% 43.27%
YanoneTagesschrift.ttf 105,016 43.12% 44.54% 45.27%
daniel.ttf 51,984 32.19% 32.03% 32.01%
danielbd.ttf 63,688 36.09% 36.05% 36.03%
danielbk.ttf 88,760 36.12% 36.14% 36.12%
journal.ttf 130,956 41.58% 42.14% 42.12%
Average: 41.48% 41.97% 42.13%

OTF to WOFF

font original, bytes gzip saves, % woff saves, % woff gzipped saves, %
Diavlo_BLACK_II_37.otf 33,964 24.99% 25.05% 24.98%
Diavlo_LIGHT_II_37.otf 33,404 24.69% 24.81% 24.78%
Fertigo_PRO.otf 52,636 32.56% 32.97% 32.90%
Fontin-Bold.otf 30,460 46.54% 46.26% 46.45%
Fontin-Italic.otf 30,636 46.31% 45.89% 46.05%
Fontin-Regular.otf 30,396 47.79% 47.41% 47.61%
Fontin-SmallCaps.otf 29,308 47.39% 46.96% 47.14%
Fontin_Sans_B_45b.otf 24,984 29.65% 30.72% 32.11%
Fontin_Sans_I_45b.otf 24,772 28.43% 29.45% 30.75%
Fontin_Sans_R_45b.otf 25,564 29.52% 30.43% 31.87%
Fontin_Sans_SC_45b.otf 27,384 33.54% 34.68% 36.04%
GraublauWeb.otf 41,464 23.96% 24.12% 24.09%
GraublauWebBold.otf 44,040 27.34% 27.40% 27.34%
Tallys_15.otf 19,996 17.96% 17.32% 17.23%
YanoneKaffeesatz-Bold.otf 55,568 60.58% 60.14% 60.29%
YanoneKaffeesatz-Light.otf 58,328 57.73% 57.70% 58.08%
YanoneKaffeesatz-Regular.otf 58,528 57.83% 57.79% 58.18%
YanoneKaffeesatz-Thin.otf 58,292 58.27% 58.25% 58.66%
pykes_peak_zero.otf 122,832 73.74% 73.21% 73.32%
vollkorn.otf 30,065 16.27% 16.54% 16.47%
Average: 45.40% 45.40% 45.68%
 

Gzip your @font-face files

Saturday, October 10th, 2009

Adding custom fancy fonts to a web page seems to be all the rage these days. Looking at some examples with Net panel on, I saw some of those font files are 100K which is a pretty big price to pay for an ornament like this. I mean you can build whole pages, with fancy scripts, images, gradients and all, that are less than that size.

Made me wonder about the sizes of those font files and in particular is there an easy gzipping win. Turns out yes - you can and should gzip the custom font files you use with @font-face.

UPDATE Oct 19, 09: Gzip all font files except those in WOFF format, more info...

Quick background

In order to include a custom font on a page you can use Paul Irish's bulletproof way, something like:

@font-face {
  font-family: 'gzipper';
  src: url(yanone.eot);
  src: local('gzipper'),
         url(yanone.ttf) format('truetype');
}

body {
    font-family: gzipper;
}

What's going on here?

  1. You give your custom font a name, like "gzipper"
  2. This line is for IE - src: url(yanone.eot);. It asks for an .eot font file
  3. This line is for pretty much every other browser url(yanone.ttf) format('truetype'). It needs a .ttf font file
  4. That last line has variations, you can use an .otf file instead of .ttf, interchangeably, like so url(yanone.otf) format('opentype')
  5. SVG font file (for Chrome) is also an option instead of, or in addition to, ttf, like: url(yanone.svg) format('svg')

So that leaves us with 4 different types of files:

  • svg
  • ttf
  • otf
  • eot

How about gzipping these?

SVG

SVG is easy, it's an XML file. XML files are plain text files so they must always be gzipped. Potential savings? Anywhere around (and I'm pulling this off my hat) 30-70%.

TTF

Turns out TTF is also mostly a text file, and it can safely be served gzipped. I tested FF 3.5, Opera 10, Safari 4, works everywhere. The test page is here.

Grabbing some files to test from here and one from here (and having my kid's tae-kwon-do lesson to sit through on this beautiful Santa Monica Saturday morning) I gzipped them and compared the results.

On average - 41.48% savings.

font file plain size, bytes gzipped size, bytes savings, %
AllerDisplay.ttf 95,616 47,771 50.04%
Aller_Bd.ttf 128,368 59,884 53.35%
Aller_BdIt.ttf 123,556 58,942 52.30%
Aller_It.ttf 120,876 58,459 51.64%
Aller_Lt.ttf 132,780 60,766 54.24%
Aller_LtIt.ttf 122,296 57,342 53.11%
Aller_Rg.ttf 134,436 63,379 52.86%
FFF_Tusj.ttf 1,543,648 1,008,083 34.69%
MarketingScript.ttf 55,160 25,026 54.63%
Sansation_Bold.ttf 19,644 10,467 46.72%
Sansation_Light.ttf 19,568 10,425 46.72%
Sansation_Regular.ttf 19,480 10,172 47.78%
Ubuntu-Title.ttf 15,108 7,473 50.54%
UglyQua-Italic.ttf 184,300 103,863 43.64%
UglyQua.ttf 120,424 59,502 50.59%
Vera-Bold-Italic.ttf 63,208 38,398 39.25%
Vera-Bold.ttf 58,716 36,644 37.59%
Vera-Italic.ttf 63,684 38,629 39.34%
Vera.ttf 65,932 40,173 39.07%
YanoneTagesschrift.ttf 105,016 59,732 43.12%
daniel.ttf 51,984 35,250 32.19%
danielbd.ttf 63,688 40,701 36.09%
danielbk.ttf 88,760 56,697 36.12%
journal.ttf 130,956 76,506 41.58%

I couldn't help but notice huge differences between different font files, even in this small sample. Sansation_Regular.ttf is 10K while FFF_Tusj.ttf is 1 Meg! So keep in mind to check the size of the font you're about to use, there might be smaller and similar fonts.

OTF

Same thing, testing with some files found here, the results are 45.40% savings on average when gzipping the .otf font files.

font file plain size, bytes gzipped size, bytes savings, %
Diavlo_BLACK_II_37.otf 33,964 25,477 24.99%
Diavlo_LIGHT_II_37.otf 33,404 25,157 24.69%
Fertigo_PRO.otf 52,636 35,498 32.56%
Fontin-Bold.otf 30,460 16,285 46.54%
Fontin-Italic.otf 30,636 16,447 46.31%
Fontin-Regular.otf 30,396 15,870 47.79%
Fontin-SmallCaps.otf 29,308 15,419 47.39%
Fontin_Sans_B_45b.otf 24,984 17,576 29.65%
Fontin_Sans_I_45b.otf 24,772 17,730 28.43%
Fontin_Sans_R_45b.otf 25,564 18,018 29.52%
Fontin_Sans_SC_45b.otf 27,384 18,200 33.54%
GraublauWeb.otf 41,464 31,528 23.96%
GraublauWebBold.otf 44,040 31,999 27.34%
Tallys_15.otf 19,996 16,404 17.96%
YanoneKaffeesatz-Bold.otf 55,568 21,904 60.58%
YanoneKaffeesatz-Light.otf 58,328 24,655 57.73%
YanoneKaffeesatz-Regular.otf 58,528 24,681 57.83%
YanoneKaffeesatz-Thin.otf 58,292 24,324 58.27%
pykes_peak_zero.otf 122,832 32,259 73.74%
vollkorn.otf 30,065 25,173 16.27%

EOT

The case with .eot is a little more interesting (like anything to do with IE, right?). Apparently MS provides software, called WEFT to create those files, but looks like people have hard time making it work.

The good thing about this tool is that it can create compressed .eot files which is excellent. You can see some examples linked to from this MSDN article. Some of those EOTs in the examples are as small as 4K which is totally acceptable. So it's worth the time to figure it out because it can save quite a bit.

The other option to create EOTs is to use the free open source tool ttf2eot and convert TTFs to EOTs. The tool doesn't create compressed files, so generally the size of the EOT is about the size of the input TTF. In this case you must gzip the .eot before you send it to the browser.

So I took my test TTFs, converted to EOT and checked the gzip savings. On average - 41.49% savings when gzipping the uncompressed .eot

font file plain size, bytes gzipped size, bytes savings, %
AllerDisplay.eot 95,806 47,867 50.04%
Aller_Bd.eot 128,530 60,201 53.16%
Aller_BdIt.eot 123,746 59,034 52.29%
Aller_It.eot 121,046 58,554 51.63%
Aller_Lt.eot 132,962 60,863 54.23%
Aller_LtIt.eot 122,490 57,437 53.11%
Aller_Rg.eot 134,594 63,480 52.84%
FFF_Tusj.eot 1,543,820 1,008,160 34.70%
MarketingScript.eot 55,352 25,105 54.64%
Sansation_Bold.eot 19,820 10,531 46.87%
Sansation_Light.eot 19,748 10,489 46.89%
Sansation_Regular.eot 19,668 10,230 47.99%
Ubuntu-Title.eot 15,298 7,533 50.76%
UglyQua-Italic.eot 184,482 103,965 43.64%
UglyQua.eot 120,594 59,598 50.58%
Vera-Bold-Italic.eot 63,458 38,467 39.38%
Vera-Bold.eot 58,934 36,712 37.71%
Vera-Italic.eot 63,914 38,696 39.46%
Vera.eot 66,142 40,249 39.15%
YanoneTagesschrift.eot 105,274 59,875 43.12%
daniel.eot 52,184 35,358 32.24%
danielbd.eot 63,892 40,815 36.12%
danielbk.eot 88,984 56,816 36.15%
journal.eot 131,180 76,536 41.66%

I tried my test page in IE6 and it worked with the gzipping, so I'm assuming IE7,8 are fine too.

Summary

  • Always send your font files gzipped, average 40% to be won, but could be 70% you never know
  • Dig into WEFT to make sure you create small, compressed EOTs
  • Keep an eye on the font file sizes you're about to use, they can be huge. When you have a choice, opt in for the 10K font file and not the 1.5 megs one

links

 

CSS munging – a FAILed experiment

Saturday, October 3rd, 2009

Not sure if I've ever put that in writing, but CSS irks me with its verboseness. I mean things like: background-position, padding-bottom, text-decoration... those are long property names, repeatedly used in stylesheets. And there's pretty much nothing you can do about them in terms of minification, it's not like they are variables in JavaScript which your YUIcompressor can rename to, like, a, b and c, respectively.

Or can you do something about it? Here's the big idea:

the big idea

I though of using JavaScript arrays to store each CSS property only once, and also each value only once. Then have another array with selectors and a way to match selector to indices in the property/value arrays. A simple loop in JavaScript can reconstruct the stylesheet. Simple, right? And avoids all these verbose repetitions.

Drawback - breaking the separation of concerns. Relying on behavior (JS) in order to get presentation (CSS). If someone has disabled JS they get no styles. That's a big no-no, but there's one case where you can safely break the separation - in lazy-loaded functionality. Here's what I mean...

Combining lazy-loaded assets

Page loads ok in its basic form - styles and all. Then it gets progressively enhanced with JavaScript. JS adds new features. If you have JS OFF, you don't get them. But if you do, it makes sense to have the feature atomic - one file containing both JS and CSS. This way saving HTTP requests (rule #1 for faster pages). That's a pretty cool idea in itself and you can find implementations in the wild, including high-traffic sites such like Google search and Yahoo homepage.

Ok, with the SoC out of the way, what about that failed experiment?

A failed experiment

(psst, demo here)

You start with something like:

#test {
    border: 1px solid blue;
    font-family: Verdana;
}
a {
    padding: 0;
    font-family: Arial;
}

Then (all that during build time, not run time!) you have a parser that will walk over the CSS and "understand it" into an array, like:

[
    {
        selector: '#test',
        rules: [
            {
                property: 'border',
                value   : '1px solid blue'
            }
        ]
    },

]

Then from that structure you produce 4 arrays that contain:

  1. all selectors,
  2. unique properties (sorted),
  3. unique values (sorted) and
  4. a map that connects selectors to their properties and values.
s:['#test','a'], // s-selectors
p:['border','font-family','padding'], // p-properties
v:['1px solid blue','Verdana',0,'Arial'], // v-values
r:[[0,0,1,1],[2,2,1,3]] // r-map or rules

Finally, your build process produces a JavaScript function that reconstruct the CSS string, which you can then shove into a style tag and be done with it.

(function(o,s,i,j){
s='';
for(i=0;i<o.s.length;i++){
  s+=o.s[i]+'{';
  for(j=0;j<o.r[i].length;j=j+2){
    s+=o.p[o.r[i][j]]+':';
    s+=o.v[o.r[i][j+1]]+';';
  }
  s+='}';
}
return s;
})({
s:['#test','a'],
p:['border','font-family','padding'],
v:['1px solid blue','Verdana',0,'Arial'],
r:[[0,0,1,1],[2,2,1,3]]
})

Results

The demo is here, you can paste your CSS and see what the results are for your styles. Here's what I found:

  • Really tiny stylesheets don't make sense, because the overhead exceeds any savings
  • Otherwise it helps! You get smaller CSS, yey! Champagne! Caviar! Everybody dance!
  • But... after gzipping the result, the AFTER is actually bigger than BEFORE. Cold shower. The hole thing goes down the drain. C'est la vie, gzip does a better job than I can do with JS arrays.

Here's the results of munging one 16K gzipped CSS found on the Yahoo! homepage;

Raw:
  source: 95735
  result: 87300
    percent: 8.81077975662%
Gzipped:
  source: 16211
  result: 16730
    percent: -3.20152982543%
 

cssmin.js

Wednesday, September 23rd, 2009

cssmin.js is a JavaScript port of YUICompressor's CSS minifier.

The motivation

Minifying CSS helps reduce file sizes and makes your pages faster and your users happier. YUICompressor is cool but is written in Java, which can be a blocker for some folks - you know JVM, command line, classpaths... No more excuses, now you have a simple light JavaScript version. And as you know, JavaScript is everywhere, so you can run it however you want, integrate with your editor and so on.

The links

The integration

If you want to integrate the library into your environment, it's really easy. It's just one file with one function in it. So, just a simple function call:

var result = YAHOO.compressor.cssmin(input_css_code);

The credits

Julien Lecomte - creator of YUICompressor
Isaac Schlueter - he maintains the YUICompressor and is the author of the original cssmin utility which was ported to Java by Julien.

Ha, what about a quiz? Guess the language of Isaac's original cssmin and I'll send you a free copy of Even Faster WebSites and I'll sign my chapter. Seriously.

UPDATE: For Ruby folks, there's a Ruby port from the Ryan Grove.

 

Sorting CSS to reduce gzip file sizes?

Wednesday, September 2nd, 2009

Yesterday I came across this post (via Ajaxian), it talks about how you might be able to end up with smaller file sizes (after gzipping) if you help gzip a little bit by sorting the CSS properties and values in stylesheets, so they are as similar and close to each other as possible.

Now obviously it's not easy to do a safe sorting, there's many edge cases to be taken care of, but the question before diving into safeness is - is it really worth it?

In attempt to answer that I did a very simple CSS parser in JavaScript that will sort the properties and values, then I tested with a few files.

Long story short - it's probably not worth it. But if you want to help, please use the online tool and report if you see a considerable benefit for your own stylesheets.

How it works

Again, bear in mind this is not safe to do, your pages might look different before and after optimization. This is just a test if alphabetizing is really worth it in terms of gzipped filesize.

Before:

b {
  b: b c a;
  a: a b c;
}

a {
  a: a;
}

After

b {
  a: a b c;
  b: a b c;
}
a {
  a: a;
}

As you can see, order of selectors is preserved, properties within each CSS block are sorted and their values are sorted too.

Help by testing your CSS

Since the original question was - is it worth modifying YUI Compressor to sort properties and values, you can help by following these two steps:

  1. minify your real-life CSS with YUI compressor (online version here) and then
  2. paste the result in my parser/sorter tool to see if you can gain much after compression.

Please share your results in the comments section of this post.

Results so far?

I tested with two files well under 10K and the results were ever-so-slightly negative - the results were 0.05% bigger.
Tested also with a few files over 10K and the results were 1.5% savings. Hardly worth it. But I hope someone can prove me wrong and show cases where alphabetizing can be well worth it. After all, the sweetest optimization is the one you don't have to do, but your build process can manage by itself ;)

 

The art and craft of postload preloads

Thursday, August 27th, 2009

What can your tired old page, once loaded and used and read, can do for your user? It can preload components needed by the next page, so when the users visit the next page, they have the new scripts, styles and images already in the cache. Next page loads faster and the user's happy. On its death bed you tired old page has left a good inheritance for future generations. Good old page.

How can you go about preloading for the next page? By waiting for onload of the current page and requesting the new components. Here are 4 ways to do so, all using a timeout of 1 second after page load so that prefetching doesn't interfere with the user experience on the page.

One way... (DOM)

Using DOM you can create a new LINK element and a new SCRIPT element and append them to the HEAD. For images - it's a one-liner new Image.src="..."

The drawback of this method is that your CSS is executed against the current page and might affect display. Same for JavaScript - it's executed. The image is simply requested and never displayed.

window.onload = function() {
    setTimeout(function(){

        // reference to <head>
        var head = document.getElementsByTagName('head')[0];

        // a new CSS
        var css = document.createElement('link');
        css.type = "text/css";
        css.rel = "stylesheet";
        css.href = "new.css";

        // a new JS
        var js = document.createElement("script");
        js.type = "text/javascript";
        js.src = "new.js";

        // preload JS and CSS
        head.appendChild(css);
        head.appendChild(js);

        // preload image
        new Image().src = "new.png";

    }, 1000);
};

DOM test page

For this way of doing it you can use any JavaScript library's helper methods to load stuff on demand. Good examples - YUI Get and LazyLoad

... or another ... (using iframe)

Another option is to create an iframe and append your components to its head. Using an iframe you can avoid the CSS potentially affecting the current page. JavaScript will still be executed.

window.onload = function() {
    setTimeout(function(){

        // create new iframe
        var iframe = document.createElement('iframe');
        iframe.setAttribute("width", "0");
        iframe.setAttribute("height", "0");
        iframe.setAttribute("frameborder", "0");
        iframe.setAttribute("name", "preload");
        iframe.id = "preload";
        iframe.src = "about:blank";
        document.body.appendChild(iframe);

        // gymnastics to get reference to the iframe document
        iframe = document.all ? document.all.preload.contentWindow : window.frames.preload;
        var doc = iframe.document;
        doc.open(); doc.writeln("<html><body></body></html>"); doc.close(); 

        // create CSS
        var css = doc.createElement('link');
        css.type = "text/css";
        css.rel = "stylesheet";
        css.href = "new.css";

        // create JS
        var js = doc.createElement("script");
        js.type = "text/javascript";
        js.src = "new.js";

        // preload CSS and JS
        doc.body.appendChild(css);
        doc.body.appendChild(js);

        // preload IMG
        new Image().src = "new.png";

    }, 1000);
};

IFRAME test page

... I'm gonna find ya ... (static page in iframe)

If your components are static you can create a page that has them all and load that page into the dynamic iframe. Static means knowing them in advance, not relying on page's JavaScript to figure them out on the fly. As you can see, much simpler than the previous code.

window.onload = function() {
    setTimeout(function(){

        // create a new frame and point to the URL of the static
        // page that has all components to preload
        var iframe = document.createElement('iframe');
        iframe.setAttribute("width", "0");
        iframe.setAttribute("height", "0");
        iframe.setAttribute("frameborder", "0");
        iframe.src = "preloader.html";
        document.body.appendChild(iframe);

    }, 1000);
};

IFRAME static page test

... I'm gonna GETcha, GETcha, GETcha! (with Ajax)

Finally - Ajax. Scripts are not executed, CSS not used for rendering. Nice and clean. For simplicty the code has no support for browsers that don't know what XMLHttpRequest is.

window.onload = function() {
    setTimeout(function(){

        // XHR to request a JS and a CSS
        var xhr = new XMLHttpRequest();
        xhr.open('GET', 'new.js');
        xhr.send('');
        xhr = new XMLHttpRequest();
        xhr.open('GET', 'new.css');
        xhr.send('');

        // preload image
        new Image().src = "new.png";

    }, 1000);
};

Ajax test page

Thanks!

Any other ways you can think of?

 

The star hack in IE8 and dynamic stylesheets

Friday, July 3rd, 2009

CSS hacks

⇓ skip if you already know about the star and underscore hacks

For most CSS tasks, there are only two hacks that are straighforward to use, easy to spot and maintain (delete down the road), easy to understand. The star hack that targets IE6 and 7 and the underscore hack that targets IE6.

Consider this:

.box {
    background: red; /* normal browsers */
    *background: blue;  /* IE 6 and 7 */
    _background: green; /* IE6 */
}

In normal browsers, you get red. Normal browsers ignore *background and _background because they are invalid. IE6 and 7 have a bug that accepts the definition prefixed with a star as if you defined it as "background". And because *background comes after background, IE6 and 7 will say "oh, the latest one overwrites". Similarly the underscore. But the _ bug was fixed in IE7 and only IE6 still treats _background as if it was background.

So the code above will paint a red box in normal browsers, blue in IE7 and green in IE6. IE8 is also a normal browser so IE8 in IE8 mode will give you red. BTW, this technique applies to all properties, not only background.

Surprise

The problem I found is when you set styles dynamically. Say you have a bunch of CSS content as a string and you want to create a new style tag and shove the code there. I've blogged about this before.

When you set styles dynamically IE8 behaves as IE7.

:( , :( and double :(

» Here's a demo/test page.

The code:

var def = ".box {background: red; *background: blue; _background: green;} ";
var ss = document.createElement('style');
ss.setAttribute("type", "text/css");
if (ss.textContent) { // FF, Safari
    ss.textContent = def;
} else {
    ss.styleSheet.cssText = def; // FF, IE
}
var head = document.getElementsByTagName('head')[0];
head.appendChild(ss);

This will give you blue in IE8, which is really unfortunate.

UPDATE: Thanks to a question from @stubbornella, I updated the test with setting the inline style attribute, as opposed to creating a new <style> tag. Turns out IE8 behaves as normal IE8 in this case and ignores the star and the underscore hacks. So only creating a new style tag dynamically via JavaScript is the problem.

 

MHTML – when you need data: URIs in IE7 and under

Friday, April 10th, 2009

UPDATE: It's very important to have a closing separator in the MHTML document, otherwise there are known issues in IE7 on Vista or Windows 7. The details are here.

In the previous post I described what data: URIs are and how they are useful to reduce the number of HTTP requests. Now, the problem with data: URIs is that they are not supported by IE < 8. But, thanks to this article (in Russian), here's a way to work around that limitation: using MHTML for IE7 and under.

MHTML-a-what?

MHTML is MIME HTML, or if you insist on me spelling it out completely is like "Multipurpose Internet Mail Extensions HyperText Markup Language". In short it's HTML but like email with attachments. In one "multipart" email you can have several... hm, things - HTML version of the email, text-only version, attachment, another attachment...

MHTML is the same but for HTML. In one file you put a bunch of stuff (e.g. image files) and you save on the precious HTTP requests.

MHTML example

Let's check out an example. The HTML is actually not important, it's the CSS where we'll stick all our inline images. Once with data: sheme for all normal browsers and once with the mhtml: scheme for IE before 8.

The overall "template" would look like so:

/*
Content-Type: multipart/related; boundary="_ANY_SEPARATOR"

--_ANY_SEPARATOR
Content-Location:somestring
Content-Transfer-Encoding:base64

iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAIAA[...snip...]SuQmCC
--_ANY_SEPARATOR--
*/

#myid {
  /* normal browsers */
  background-image: url("data:image/png;base64,iVBORw0[...snip...]");
  /* IE < 8 targeted with the star hack */
  *background-image: url(mhtml:http://phpied.com/mhtml.css!somestring);
}

The multipart stuff goes into a CSS comment. You then use data: URI scheme for all normal browsers. Then you use the star hack to target IE browsers before 8. For these browsers you give the URL of the CSS, exclamation and then a string that uniquely identifies the desired "part" in the multipart comment. Simple, eh? Didn't think so, but hey, it's not exactly rocket science.

Demo example with more parts

Check out a demo page that has two "parts" in the multipart MHTML comment.

The HTML is noting special and the CSS goes like:

/*
Content-Type: multipart/related; boundary="_ANY_STRING_WILL_DO_AS_A_SEPARATOR"

--_ANY_STRING_WILL_DO_AS_A_SEPARATOR
Content-Location:locoloco
Content-Transfer-Encoding:base64

iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAIAAAA7ljmRAAAAGElEQVQIW2P4DwcMDAxAfBvMAhEQMYgcACEHG8ELxtbPAAAAAElFTkSuQmCC
--_ANY_STRING_WILL_DO_AS_A_SEPARATOR
Content-Location:polloloco
Content-Transfer-Encoding:base64

iVBORw0KGgoAAAANSUhEUgAAABkAAAAUBAMAAACKWYuOAAAAMFBMVEX///92dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnYvD4PNAAAAD3RSTlMAACTkfhvbh3iEewTtxBIFliR3AAAAUklEQVQY02NgIBMwijgKCgrAef5fkHnz/y9E4kn+/4XEE6z/34jEE///A4knev7zAwQv7L8RQk40/7MiggeUQpjJff+zIpINykbIbhFSROIRDQAWUhW2oXLWAQAAAABJRU5ErkJggg==
--_ANY_STRING_WILL_DO_AS_A_SEPARATOR--
*/

#test1 {
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAIAAAA7ljmRAAAAGElEQVQIW2P4DwcMDAxAfBvMAhEQMYgcACEHG8ELxtbPAAAAAElFTkSuQmCC"); /* normal */
  *background-image: url(mhtml:http://phpied.com/files/mhtml/mhtml.css!locoloco); /* IE < 8 */
}

#test2 {
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAUBAMAAACKWYuOAAAAMFBMVEX///92dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnYvD4PNAAAAD3RSTlMAACTkfhvbh3iEewTtxBIFliR3AAAAUklEQVQY02NgIBMwijgKCgrAef5fkHnz/y9E4kn+/4XEE6z/34jEE///A4knev7zAwQv7L8RQk40/7MiggeUQpjJff+zIpINykbIbhFSROIRDQAWUhW2oXLWAQAAAABJRU5ErkJggg=="); /* normal */
  *background-image: url(mhtml:http://phpied.com/files/mhtml/mhtml.css!polloloco); /* IE < 8 */
}

div {
  width: 100px;
  height: 100px;
  font: bold 24px Arial;
}

Drawback

In addition to the drawbacks of data: URIs (bigger CSS, small CSS change invalidates your cached inline images), this method has the obvious drawback that the inline images are repeated twice. But sometimes... a person's gotta do what a person's gotta do ;)

 

>>> $$(selector)

Friday, January 30th, 2009

Gotta love the Firebug console, how can anyone not love the Firebug console. It makes testing random pieces of JavaScript a breeze and best of all - you're playing with the live page. Your page or any page for that matter.

Two nice shortcuts you can use in the console are $ and $$.

The first is like document.getElementById() and the second allows you to get elements by using a selector, like w3c's document.querySelectorAll(), now available in the latest browser versions, including IE8.

So go ahead, give $$ a try. For example you can visit yahoo.com, open up the console and try:
>>> $$('.first')
or
>>> $$('.patabs .first')
or
>>> $$('#tabs1 li')

Lots of fun!

So here's a little example application I came up with, it spits out unused selectors from your CSS. Just paste it in the multi-line console.

for(var i = 0; i < document.styleSheets.length; i++) {
  for (var j = 0; j < document.styleSheets[i].cssRules.length; j++) {
    s = document.styleSheets[i].cssRules[j].selectorText;
    if ($$(s).length === 0) console.log(s);
  }
}
 

Background repeat and CSS sprites

Thursday, April 3rd, 2008

CSS sprites are a great way to improve the loading speed of your pages. One of the problems you might face with sprites is how to deal with cases when the background repeats.

The rule is pretty simple: if you want the background to repeat vertically (top to bottom), place the images in the sprite horizontally (left to right) and make sure the individual images in the sprite have the same height. For the opposite case: when you want to repeat horizontally, sprite vertically.

Example

» Demo

You have three divs and you want to repeat their background vertically, so that the background takes the whole height of the div, according to the content.

Source images:

  1. 1.png
  2. 2.png
  3. 3.png

Singe image sprite:

sprite8.png

CSS definitions to make this work:

    div {
        background-image:url(sprite8.png);
        background-repeat: repeat-y;
        width: 160px;
        padding: 20px;
        margin: 20px;
        float: left;
    }

    #my {
        background-position: 0px;
    }
    #my2 {
        background-position: -200px;
    }
    #my3 {
        background-position: -400px;
    }

Every image is 200px wide. Because of the 2x20px margin left and right, the width is 160px (=200 - 20 - 20)

The tree divs have IDs: my, my2, my3

Result

Once again, the result

Tested on Windows with IE7, FF, O, S.

 

The Front-end Cerberus

Thursday, October 25th, 2007

Some smart guys picture the distinction of content (HTML), presentation (CSS) and behaviour (JavaScript) as a three-legged stool. This is totally fine, but can't we draw a more heroic picture of today's Front-end developer?

frontend-cerberus.jpg

I found the image here, if anyone knows the original author, let me know so I can give proper credit.

BTW, I never knew what Cerberus meant until 15 minutes ago. The thing is that where I come from, we used this name to refer to some of the teachers that weren't very nice to us at school :)

 

Quick CSSSprites.com stats

Wednesday, September 12th, 2007

Just deleted about 3 gigs of files uploaded to csssprites.com because I'm running out of disk space. I keep the .html results though, just for stats purposes. So ls | wc -l says that there are 3170 files meaning over 3000 sprite images were produced already, nice.

The tool is very primitive, I admit, its main purpose was to raise awareness of the CSS sprites technique. It's missing a lot of features (as any weekend project do), but I'd say it allows developers to start quickly with the sprites and to admire faster loading web sites in less seconds than it takes to say "OK, how I have open Photoshop and to create one big image of all my smaller images and write down the coordinates in a css file".

 

csssprites.com update

Sunday, July 22nd, 2007

Added:
- generation of a GIF sprite in addition to the PNG
- small client-side only check if at least two files are updated
- link to a Yahoo search for "css sprites"
- note begging people not to upload huge files

Only the first thing is a feature, the other three are to raise awareness on what the CSS sprites technique is about. Hopefully it will help people stop uploading 20-30 megs of images in order to create a sprite.

http://www.csssprites.com/

 

phpBB front-end optimization – 1 hour workshop

Friday, July 13th, 2007

Let's go ahead and optimize our phpBB installation for front-end performance. I'll follow Yahoo's 14 optimization rules, but only implement the ones that apply for phpBB. During this short workshop there will be no changes to the phpBB code, we'll create a new template instead, so that in case something bad happens, your board will continue to work normally. As an example, I'll use the bgcanada.com/phpBB2, the board I volunteer to administrate.

The plan

1. creating two subdomains to host assets - images and css, maximizing parallel downloads (also following rule #9)
2. creating a new template (theme) based on the default subSilver template
3. moving CSS to an external file (rule #8), merging the two css files (rule #1)
4. copying images and css to the new subdomains
5. making sure css is served gzipped (rule #4) and also making sure php pages are served gzipped)
6. turning ETags off (rule #13)
7. setting the Expires header (rule #3)
There will be no explanations on the reasoning why these rules exists, but only how to implement the applicable ones in phpBB. It's a good idea though to go through the links above and read the detailed description of the rules. Maybe you'll find something that can be done in addition to the plan above. The suggested implementations were done and tested on a normal $60/year shared host, working around the limitations of such hosting. I'll speculate that at least 90% of the phpBB installations out there use shared host so I hope my implementation will be relevant to your phpBB install as well.

New subdomains

Typically, shared hosts allow subdomains and have their admin interface for doing so. In case your host is an exception, alternatively you can buy one or two extra domains to use for the same purpose - storing page assets.

So in my case, for the domain bgcanada.com I create two sub-domains - i1.bgcanada.com and i2.bgcanada.com (i as in image). Now the question would be how to divide assets between the two domains. I decided that more or less half of the images are multilingual (they don't contain text) so these go to i2. i1 will have all the rest - translatable images found in subSilver/images/lang_english, smilies and the stylesheet.

creating a new template

Let's go ahead and create a new template (theme), based on the default subSilver template. I'll call mine "sso" as in "subSilver optimized" (or the longer mirror-like version "ssoss" as in "stoyan stefanov, oh, so smart" ;) )

For this purpose, just take the contents of path/to/phpbb/templates/subSilver and copy it under the name "sso", so you'll have /templates/sso, an exact subSilver copy.

Now go ahead and modify templates/sso/subSilver.cfg, the theme configuration. First rename it to sso.cfg. Then at the top of the document, replace the line:

$current_template_images = $current_template_path . "/images";

with

//$current_template_images = $current_template_path . "/images";
$i1 = "http://i1.bgcanada.com/sso"; // language and smilies
$i2 = "http://i2.bgcanada.com/sso"; // the rest

use your domain, of course.

Then do a search/replace. All occurrences of:

$current_template_images/{LANG}/

should become

$i1/{LANG}/

And all occurrences of

$current_template_images/

become

$i2/

At the end of the file, just before ?> add a new line:

$board_config['smilies_path'] = 'http://i1.bgcanada.com/smilies';

This will overwrite the path to the smilies you set in the admin interface. This is optional, you can achieve the same by using the admin panel, but we said we wanted to have the board unaffected by the changes.

Now open templates/sso/theme_info.cfg and replace all occurrences of "subSilver" with "sso".

Now the bad news is that some of the template files (.tpl) still contain relative paths to images, we need to make these absolute and pointing to the subdomians. Since these paths are hardcoded in the templates we're sure that they are language independent, so they'll all go to the i2 subdomain. If you have a good text editor that can search/replace in multiple files, go ahead. Alternatively, use the php script I came up with. Download it, copy it to your /templates/sso folder, rename to replace.php and use it like:
C:\path\to\phpbb\templates\sso> php replace.php
It will report what it replaced.
It only searches for templates/subSilver/images and replaces with http://i2.bgcanada.com/sso

Yep, almost done with the template, one last step - the css.

Moving CSS to an external file, merging

In the default subSilver there are style definitions in overall_header.tpl. Remove them, the whole thing between the <style> tags and replace with:

<link rel="stylesheet" href="http://i1.bgcanada.com/sso.css" type="text/css">

Now rename subSilver.css to sso.css. Copy the contents of formIE.css and append it to the end of sso.css. Optionally, you can walk through the new file and strip all comments and white space. Or use my stripped version.

Note that as a side effect, the font of the board will look somewhat different, because the definitions in subSilver.css are not exactly the same as those in the overall_header.tpl. It's not a big difference, but if it's important to you, just ignore the original subSilver.css and create sso.css copying the styles from overall_header.tpl and formIE.css.

We're done with the files, the rest is sysadmin stuff.

copying files to the new subdomains

That's easy, just take everything from templates/sso/images (leave the lang_english or any other language folders) and copy to http://i2.bgcanada.com/sso

Than take all lang_* folders and copy to http://i1.bgcanada.com/sso, so you'll have http://i1.bgcanada.com/sso/lang_english/

Now copy all smilies from phpbbroot/images/smilies to http://i1.bgcanada.com/smilies/

Now take the sso.css you created and copy to http://i1.bgcanada.com/sso.css

Last, take the whole sso directory and copy to the main domain - http://www.bgcanada.com/phpBB2/templates/ so that it's in the same folder next to subSilver.

Login to the admin panel and activate the new theme as usual. Login to your account and change your theme preference to sso. You still want to test before making it a default theme for everyone.

At this point you should be able to browse your forum with the new theme and everything should look like as if you were using subSilver.

Serving gzipped content

The rule is that all served files should be gzipped, with exception for images, because gifs, jpgs, pngs are already compressed.

To serve the normal html (php) pages gzipped, just use the built-in phpBB feature, log on to the admin panel and enable gzip compression.

To serve sso.css gzipped, ideally you should only create an .htaccess file and have Apache do it for you (more info). Unfortunately my host won't allow it, so I took the alternative path - have PHP gzip and serve the css file. To do so I created an .htaccess file http://i1.bgcanada.com/.htaccess and put in it:

AddHandler application/x-httpd-php .css

This affects all CSS files (but we have only one) and makes them php scripts. If your host allows you to use php_value in .htaccess, you can do the rest of the job by only using .htaccess. Otherwise create a http://i1.bgcanada.com/php.ini file and put in it:

[PHP]
default_mimetype = "text/css"
zlib.output_compression = On
zlib.output_compression_level = 6
expose_php = Off
auto_prepend_file = "pre.php"

The first line makes php send text/css header instead of the default text/html. Some browsers won't like CSS files served with text/html header. The second line enables compression and the next line sets the compression level (could be up to 9). 4th line is absolutely optional, just removes an extra header that PHP sends. The last line sets that all served php files should include the file pre.php as if you used include "pre.php" inside every PHP script. This is actually used later for the Expires header.

turning ETags off

That's super easy. Just add .htaccess rules in both i1 and i2 to say:
FileETag none

Expires header

To set the expires header, add these lines to .htaccess on both i1 and i2

ExpiresActive On
ExpiresDefault "access plus 2 years"

This makes all files - images and CSS - on i1 and i2 expire in 2 years, if that's too much/not enough, feel free to change.

If it doesn't work for you (it didn't for me, because of the host), you can go the php auto_prepend route described above and add in the pre.php file
header('Expires: ' .gmdate("D, d M Y H:i:s",time() + (60 * 60)) . ' GMT');
Feel free to set 60 * 60 to whatever you think makes sense for you. Note that this will only affect the CSS file, images will be served "normally". You can also have PHP serve all the images, like we did for CSS, but I think it's probably too much.

Sysadmin summary

There was a lot of if-then above so let me summarize what worked for me, but try your other options first to see how "liberal" your host is in order to do a better job than me.

http://i1.bgcanada.com/.htaccess

AddHandler application/x-httpd-php .css
FileETag none

http://i1.bgcanada.com/php.ini

[PHP]
default_mimetype = "text/css"
zlib.output_compression = On
zlib.output_compression_level = 6
expose_php = Off
auto_prepend_file = "pre.php"

http://i1.bgcanada.com/pre.php

<?php
header('Expires: ' .gmdate("D, d M Y H:i:s",time() + (60 * 60)) . ' GMT');
?>

Done!

We're done! Did it take more than an hour? Hope not, although with these computers and stuff, everything takes more time than expected.

Do you see anything missing? Or something that can be improved? Or something didn't work for you? Please post a comment. The whole posting is a bit fast-paced and written in a rush (I really need to go to bed), so if there's something unclear please ask.

 

CSS Sprites generation tool

Wednesday, June 27th, 2007

Here's my last weekend's project - a web-based tool to generate images for CSS sprites: http://www.csssprites.com. Cool domain name, eh? I couldn't believe it was not taken.

CSS Spr...what?

This is a simple technique used for page load performance purposes. Since HTTP requests are the most expensive thing you can do in regards to front-end performance, you strive for making as little requests as possible. So instead of having the browser download 4 rounded corner images for example, you create one image that has all four. Then you use CSS' background-position to only show the part of the image you want. More on the subject in this ALA article

How does the tool work

You upload as many images as you want and the tool generates a mosaic of all images, gives you the result as PNG and gives you the coordinates you need to use in the background-position declaration. The tool also gives you an html page as an example, so you can save both the PNG and the html page for reference.

Image size

If you properly optimize the big image, you might actually have smaller size than all the individual images combined. In my tool, the PNG image generated is not optimized at all, I leave this to you to use PNGOUT or any other tool you know. Also you can convert the PNG into GIF if that's better for your purposes.

Implementation - PHP

The PHP code is fairly simple. The actual spriting (is that a word?) class takes a list of images and calls getimagesize() on each one to get the dimensions. The image with the biggest height is used as distance between images. The rest is just composing the imagemagick command that will to the work. Here's the important method:

<?php
function combine() {
    if ($this->distance === false) {
        $distance = $this->_biggest;
    } else {
        $distance = (int)$this->distance;
    }

    if ($this->output_dir === false) {
        $output_dir = $this->_dir;
    } else {
        $output_dir = $this->output_dir;
    }

    $half = ceil($distance / 2);

    $coord = array();
    $y = 0;

    foreach ($this->images as $i=>$data) {
        $this->images[$i]['x'] = $half;
        $this->images[$i]['y'] = $half + $y;
        $coord[] = '-page +0+' . $y . ' ' . $i;
        $y += $data[1] + $distance;
    }

    $cmd = 'convert ' . implode(' ', $coord)
         . ' -background none -mosaic -bordercolor none -border '
         . $half . 'x' . $half
         . ' ' . $output_dir . '/result.' . $this->output_format;
    system($cmd, $ret);

    return $ret === 0;

}
?>

Implementation - JS

In the spirit of web2 I couldn't afford a complete page reload :lol: although it would've been much simpler. I just had to get fancy. YUI to the rescue. On page load I set up the form for async request, using YAHOO.util.Connection. In case of file uploads YUI generates an iframe behind the scenes and uploads to the iframe. Then it takes whatever is in the body on the iframe and gives it to you instead of the XMLHttpRequest's responseText property.

So the files are uploaded to upload.php which calls the class that has the method mentioned above then loops through the $images property of the said class and writes the example html file as well as prints out a JSON string with the same image information.

YUI's Connection calls my callback function and I get the invaluable responseText. Parsing the JSON with the json.js, I get a JS object. Looping through it and DOM-generating a table of results is the semi-last step. The last is (we're fancy, remember?) to yellow-fade the background color of the result, using YAHOO.util.Animation.

BTW, I got fancy once again and combined and minified json.js with my JS file, so that there is one less request and a side effect impossible to read. The unminified version of the JS that does all the work is here for reference.

Comments

I hope this tool cane be useful for quickly generating those sprites, if only for prototyping purposes. Any comments, requests, bug reports are all very welcome.

And how do you like the version of the tool? Anyone n00b can do "beta", it takes a true h@x0r (or something) to do a better job :D

Ah, yeah, and the page badly needs a stylesheet, do you want to help?

 

Smart browsers don’t download unneeded images

Saturday, June 23rd, 2007

We ofter complain about browsers, browser inconsistencies, quirks, hickups, the list goes on. Let's say something nice to them, and hope for good karma :)

It happens as your web app grows in size and team members that some parts of the stylesheets become obsolete, no one remembers why they were there in the first place, but no one has cojones to remove them, because of fear that removing them might break something you forgot to take into consideration. Same case when you have the same stylesheet for 50 different pages and on some pages only some of the styles are actually used. I was wondering, do you pay performance penalty when you have style rules that refer to images, but the rules are never needed to render the page? Will the browser download all those unneeded images? The answer is No. The browser will try to avoid downloading the image assets as much as possible. And this behavior is pleasantly consistent across IE, FF, Safari (for Windows), Opera.

Test #1

I tried this stylesheet:

body {
    background: url(bg.png);
}
#some-id {
    background: url(ie.png);
}
.some-class-name {
    background: url(o.png);
}
p {
    background: url(image.php);
}

And applied it to a blank page that doesn't have any paragraphs, or elements with class name some-class-name, or ID some-id. The result was that only bg.png was downloaded, which is great.

> test #1

Test #2

I added a button which, when clicked, creates a new P element and sets a 3 seconds timeout before appending the new element to the body. (The background for the P element is the script image.php which only sleeps for 3 seconds for demonstration purposes, before redirecting to ff.png.) The result is that the image is requested only after the element is added to the document flow, not when it was created. Which is smart. It ain't added 'till it's added.

> test #2 (same file as test #1, btw)

Test #3

OK, modified test #2. A P element is created, its visibility is set to hidden and it's added to the document. It's officially in the document flow, although not currently displayed. My guess was that the browsers will download the image at this point. True for FF, IE7, Safari, Opera.

> test #3

Test #4

Here's the only inconsistence in the behavior of the browsers. A P element is created, but with display: none, then added to the document. So it's part of the DOM tree, but not really part of the document flow. Then another button changes display to block.

FF will wait for the display: block before downloading the image background
Opera will also wait until display: block
IE7 begins to crack under the pressure and downloads the image even with display: none
So is Safari

> test #4

So what?

Well, it's nice to know that the browsers won't download the images referred to from our stylesheets unless they are needed for rendering. There is a side effect though. If you happen to create dynamic elements that have some styles with background and no other similar elements already exists on the page, the user experience might suffer slightly as the image will generally be requested after the new element is added. In cases when this is important to you, you might need to take some extra care to make sure the image is downloaded before you add the new element. You might need to do for example a new Image() after initial page load. (reminds me of the good old days of image rollover buttons, those were the days, eh? Seems like no one does image rollover buttons and menus anymore. It's all css this, css that... ;) )

 

Delay loading your print CSS

Sunday, June 17th, 2007

So you have two stylesheets on your page, like this:

<link type="text/css" rel="stylesheet" href="screen.css"
      media="screen" />
<link type="text/css" rel="stylesheet" href="print.css"
      media="print" />

The first one is used to render the page on screen, the other one is used for printing the page and print previewing it. Good.

The thing is, when it comes to performance, the browser won't render any part of the page, until all stylesheets are downloaded (fiddled with here). That includes, unfortunately, stylesheets not designed for the currently rendered media. In other words, the browser won't display your page, until the print stylesheet is also downloaded, although it's not used at all for displaying the page. That sucks and should really be addressed in future browser versions.

Test

I did a test page to check this, it's here - print.php. It includes two stylesheets, the first one intentionally sleep()s for 5 seconds, the second one - for 10 seconds.

The result is that in both Firefox and IE it takes 15 seconds for this page to be rendered. Here's the Firebug picture:

media-print.png

In Safari on Windows, it only took 10 seconds the first time around, as both stylesheets were downloaded simultaneously. Good. The bad is that after refresh, the first CSS was not even requested, I tried it a few times, actually sometimes I got the error "The error was: “unknown error” ((null):10053) ", but hey, this is the first release of the browser, it can't be perfect. Actually after I shut down Fiddler, which is what I used to monitor the HTTP traffic, the page was back to normal, so it's not clear who's to blame.

So?

Well, in order to increase rendering performance, all stylesheets not absolutely needed to initially render a page should be loaded after the page load, in the background. Once the user has a fast rendered page to interact with, you can load the additional CSS (and JavaScripts for that matter) in the background, using script and style DOM includes.

Update: From my comment bellow - a better option is to include the print css as part of the main css:
@media print {…}

 

Center an image with CSS

Monday, February 19th, 2007

Here's one solution to centering an image both horizontally and vertically, when you know the height of the container. Tested in FF, IE6, IE7

The markup:

<div class="container">
    <img src="pearbook.png" />
</div>

Styles for normal browsers:

.container {
    height: 200px;
    width: 300px;
    display: table-cell;
    text-align: center;
    vertical-align: middle;
    border: 1px solid red;
}
.contain img {
    vertical-align: middle;
}

and IE exception:

<!--[if IE]>
<style type="text/css">
    .container {
        font-size: 181px;
    }
</style>
<![endif]-->

You might wonder where 181px come from? Well, this is the height of the div that contains the image, multiplied by 0.905. Crazy, eh? But it works! And why 0.905? No idea, I saw that in a blog posting which I have hard time locating now in order to give proper credit. A guy was experimenting and he came up with this coeficient.

Here's a test page.

 

User stylesheet in IE

Saturday, January 20th, 2007

Let's say you want to quickly try out some small stylesheet changes, but you don't want to (or prefer not to, or for some reason temporarily you just can't) modify your application's CSS file(s). In FF it's easy - you have Firebug and you can play with styles until blue in the face. And in case you do get blue in the face and start making so many changes that you get lost, you can create a new clean and tidy CSS file, place it on your hard drive and use Web Developer extension to load it (Menu CSS->Add User Style Sheet). With WebDev Extension you can also Edit CSS right there, although unfortunatelly it's not always working when you have frames.

OK, there are options for Firefox. But how about IE?

In IE you have IE Developer Toolbar, definitelly helpful, but you can only modify element styles, not the stylesheet rules. So? A tiny little bookmarklet to the rescue!

My bookmarklet assumes you have a file called C:\user.css and loads this stylesheet on demand in your page, in every frame, just in case you use frames. Simple, yet useful, I hope. Here's the (readable) code:

javascript:
var css_file = prompt('Which CSS file you want to load today?','c:/user.css');
function addcss(w) {
    var html_doc = w.document.getElementsByTagName('head')[0];
    var css = w.document.createElement('link');
    css.setAttribute('rel', 'stylesheet');
    css.setAttribute('type', 'text/css');
    css.setAttribute('href', css_file);
    html_doc.appendChild(css);
}
var errors = 0;
function checkFrames(w) {
  if(w.frames && w.frames.length>0){
    for(var i=0;i<w.frames.length;i++){
      var fr=w.frames[i];
      try {
        addcss(fr);
      } catch (e) {
        errors++;
      }
      checkFrames(fr);
    }
  }
}
checkFrames(window);
addcss(window);
if (errors > 0) {
    alert('Could not access ' + errors + ' frame(s)');
}

To install and play around

Right-click this link and add it to your favourites:

Add User StyleSheet

Have in mind that this is IE-only (tested IE7). I don't think FF will allow you to access files on your local drive like this. But for FF you have the tools to do this anyway.

Another option to load local stylesheets in IE is to use the user CSS capability built in the browser, you can find it under Tools/Internet Options/Accessibility, but this will load your user CSS first (as opposed to last as the case is with my bookmarklet), so the "real" style definitions will overwrite yours, unless you always use !important and the "real" styles don't.

Thanks!

Have fun with the custom CSS and let me know how you find it.

 

Y! homepage – CSS sprites in action

Friday, December 1st, 2006

Have you looked at the HTML markup of the new Yahoo homepage? Then you should. The markup (although it won't validate) is a piece of semantic art. Lists are lists, tabs are lists, only one table to be seen (obviously plugged-in coming from a different site)

The total number of markup elements on the page (document.getElementsByTagName(*).length) is 662, which is not bad for such a busy page. Compare that with Google search results page, which is semantically pretty much nothing but a just a list and uses 468 elements to display the content, among which there are 22 tables. Amazon's home page has the stunning 1469 elements.

Anyway, the thing that I saw and liked, was the use of the so-called CSS sprites (tut, tut, demo). It's a technique where instead of creating multiple images (10 little icons for example), you create one image file that has them all. Then you use CSS's background-position to only show the part of the big image that contains the icon you want. This may look like too much of a hassle, but when you think about the number of HTTP requests you save by getting one image instead of ten, then it starts making sense. Y! proves this point by using this technique on its homepage. Since the technique is used on what is probably one of the top visited pages on the web, I would considered it production-ready :)

You can get an idea of how Y! homepage works with its image assets by using Firefox's Web Developer extension: "Images -> View Image Information". In case you don't browse with Firefox packed with Web Developer extension (then you should!), then you can check the copy that I made - copy is here. Get a load of this guy for example.

Updated Dec 02, 2006:
Just deleted one comment by mistake (I got so much spam comments), pointing out that the correct syntax is:
document.getElementsByTagName("*").length
where * is quoted.
This is true, a typo on my part.

Thank you very much Philip, I'm so sorry I deleted your comment :(

 

Rendering styles

Wednesday, October 4th, 2006

The question is - what will a browser do, given a page with several stylesheets, each of them probably overwriting definitions from the previous ones? Will the browser render the page using the first received css file, while downloading the other ones and after that partially re-rendering where required? The answer is: no, the browser will wait until all CSS files are downloaded, (then probably merge all definitions, just a wild guess) and will render once.

Test

I did this test - one page with two stylesheets that contain pretty much the same selectors for different table styles (thanks to this gallery). Each of the CSS files is actually a PHP script and has a call to sleep(), one sleeps for 5 seconds, the other one for 10.

Result

The browser sits there and waits for the both styles, rendering nothing (except for the page title, but that's not really rendering, is it?). So nothing happens for 10 seconds, then the second style is used for the final rendering. This happens in both FF and IE.

Misc

I also tried sleeping in the actual page, and flushing the output after each row. In my home environment FF renders each row as it's received, but in my hosted environment, it waits for the whole table. IE alsways waits for the complete table.

If I put the page to too much sleep so that the php script dies before the second stylesheet is dowloaded, the browser uses whatever is at hand (css1) to render the page.

Demo/download