Archive for the 'CSS' Category

A rounded corner

Wednesday, April 13th, 2011

Warning: not practical blog post, don't read, move on.

So this is a post about a thought I had - creating rounded corners in IE678 by using roundness that they already have built-in, meaning the character O.

But first:

  1. My opinion is that browsers that don't support border-radius should never ever get rounded corners. Let them rest in peace.
  2. Don't use the technique below, it's just a thought. Plus it only has one corner

Demo

Live page demo and a screenshot in IE6:

IE rounded corner

Sales pitch

  • Rounded corners in IE 6, 7, 8
  • No images
  • No JavaScript (a tiny self-rewriting CSS expression doesn't count)
  • No extra markup

The drawbacks later.

The big idea

Use the letter O in monospace and position it in the corner.

Implementation

Markup

As promised, nothing to see here:

<p>Hello world</p>

JavaScript

None. *

* The catch here is that a tiny piece of JavaScript exists as a CSS expression. It's there to trade for clean markup. You can remove it, but then you need a bit of extra markup.

CSS

p {
  /* blah-blah, border, padding... */
  border-radius: 16px; /* for good browsers */
  background-image: expression(...); /* IE[678] */
}

The expression goes like:

this.runtimeStyle.backgroundImage="none",this.innerHTML += "<b>O</b>"

(Expression stolen from Thierry, btw)

The first part of the expression overwrites itself for performance reasons. You know that expressions are bad, cause they execute too often. Well, this.runtimeStyle shuts down the expression. If you wonder, runtimeStyle is IE thing which makes styles even more specific than inline style attributes. And this refers to the HTML element, in our case the P.

The second part of the expression (note the , separator, that's kinda funny) updates the innerHTML of the P adding a B element. So the end result of running the expression at initial page load is DOM like:

<p>Hello world<b>O</b></p>

And if you prefer, you can put that markup and get rid of the CSS expression.

The rest of the CSS is just wrestling to position the O in the corner:

b {
  background: white;
  display: block;
  font-family: monospace;
  font-size: 72px;
  font-weight: bold;
  height: 41px;
  left: -18px;
  overflow: hidden;
  position: relative;
  top: -74px;
  width: 25px;
}

And this is it.

Drawbacks

I'm sure my critical readers can think of drawbacks but let me start:

  • In general principle, why would you care about rounded corners in browsers that don't know about border-radius?
  • You can't have different background inside and outside the box, because you can't style the inside of the O with a color different than the outside. However you might be able to find a character that can.
  • Playing with font sizes and positions is tricky. However there's probably a better way to position the O
  • If you managed to select text outside the "hello world" (if you do Select-All for example) to copy, you'll paste "hello worldO" :) Which is exactly what screen readers will read and your page might sound like a weirdO

So there

Maybe someone else have already thought of the idea of using a character as a corner (and has a better implementation), but that's all from me. I'm not recommending this approach, just an itch I needed to put out there. Thanks for reading!

 

When is a stylesheet really loaded?

Thursday, March 17th, 2011

Often we want to load a CSS file on-demand by inserting a link node. And we want to know when the file finished loading in order to call a callback function for example.

Long story short: turns out this is harder than it should be and really unnecessary hard in Firefox. I hereby beg on behalf of many frustrated developers: please, Firefox 4, please fire a load event when a stylesheet loads.

Update:
If you think this is "a change we can believe in", tell the browser vendors to fire a load event on link elements as the HTML5 standard mandates. Here's the list of bugs for each browser to comment on and point people to:

Here's what the standard says:

"Once the attempts to obtain the resource and its critical subresources are complete, the user agent must, if the loads were successful, queue a task to fire a simple event named load at the link element"

With that out of the way, let's see what we have here.

// my callback function
// which relies on CSS being loaded
function CSSDone() {
  alert('zOMG, CSS is done');
}

// load me some stylesheet
var url = "http://tools.w3clubs.com/pagr/1.sleep-1.css",
    head = document.getElementsByTagName('head')[0];
    link = document.createElement('link');

link.type = "text/css";
link.rel = "stylesheet"
link.href = url;

// MAGIC
// call CSSDone() when CSS arrives

head.appendChild(link);

Options for the magic part, sorted from nice-and-easy to ridiculous

  1. listen to link.onload
  2. listen to link.addEventListener('load')
  3. listen to link.onreadystatechange
  4. setTimeout and check for changes in document.styleSheets
  5. setTimeout and check for changes in the styling of a specific element you create but style with the new CSS

5th option is too crazy and assumes you have control over the content of the CSS, so forget it. Plus it checks for current styles in a timeout meaning it will flush the reflow queue and can be potentially slow. The slower the CSS to arrive, the more reflows. So, really, forget it.

So how about implementing the magic?

  // MAGIC
  // #1
  link.onload = function () {
    CSSDone('onload listener');
  }
  // #2
  if (link.addEventListener) {
    link.addEventListener('load', function() {
      CSSDone("DOM's load event");
    }, false);
  }
  // #3
  link.onreadystatechange = function() {
    var state = link.readyState;
    if (state === 'loaded' || state === 'complete') {
      link.onreadystatechange = null;
      CSSDone("onreadystatechange");
    }
  };

  // #4
  var cssnum = document.styleSheets.length;
  var ti = setInterval(function() {
    if (document.styleSheets.length > cssnum) {
      // needs more work when you load a bunch of CSS files quickly
      // e.g. loop from cssnum to the new length, looking
      // for the document.styleSheets[n].href === url
      // ...

      // FF changes the length prematurely :( )
      CSSDone('listening to styleSheets.length change');
      clearInterval(ti);

    }
  }, 10);

  // MAGIC ends

Test

Test page - riiiight here. I'm loading a CSS file delayed two seconds on the server. Attaching all those event listeners and timeouts above. Adding another timeout that just says "... and two seconds later ... " after (you guessed it!) two seconds. Now observing los resultados...

Results

  • IE fires readystatechange and load (tested years ago, too lazy to test now again). Now with IE9 maybe addEventListener will work too?
  • Firefox (like before) fires nothing. It updates the length of document.styleSheets immediately, not waiting for the file to actually arrive. So the outcome in my test log is:
    zOMG, CSS #1 is done: listening to styleSheets.length change
    ... and two seconds later ...
    
  • Opera fires load via onload and via addEventListener too. Like FF it also increments document.styleSheets.length immédiatement. The outcome:
    zOMG, CSS #1 is done: listening to styleSheets.length change
    ... and two seconds later ...
    zOMG, CSS #1 is done: onload listener
    zOMG, CSS #1 is done: DOM's load event
  • Chrome and Safari will not fire events but will update document.styleSheets only when the file arrives, yey!
    ... and two seconds later ...
    zOMG, CSS #1 is done: listening to styleSheets.length change

All in all, there's at least one way to tell when the stylesheet is loaded in each browser, except Firefox. Now that's embarrassing.

Is there really no hope for Firefox?

If you go really crazy than yes - implement magic #5 but it has serious drawbacks.

Otherwise the object trick should do - all browsers seem to fire load and/or readystatechange event consistently. Obviously it's a little more complicated. Although probably not as complicated as monitoring the document.styleSheets collection

I also tried MozAfterPaint - it might work but didn't for me, because my CSS didn't change anything on the page that required repaint. Obviously not a fit-all solution.

Another thing that failed was checking document.styleSheets[n].cssRules. Although document.styleSheets.length is updated immediately, I was thinking FF cannot update document.styleSheets[n].cssRules (document.styleSheets[n].sheet.cssRules) until the CSS actually arrives. However since FF 3.5 (or thereabouts) you don't have access to cssRules collection when the file is hosted on a different domain (CDN say). Security thing, you see. Even cssRules.length would've been enough, but nah.

Takeaways

  1. Any ideas about a clever (or not so clever) workaround that lets us figure out when CSS is loaded in FF? Please comment.
  2. Libraries can benefit from document.styleSheets.length trick to support Chrome and Safari. I know at least that YUI3 doesn't support callbacks on Y.Get.css() in Safari (nor FF)
  3. Firefox 4 must implement load on stylesheets :) No doubt about it. IMO all browsers should fire load on everything related to external resources.

UPDATE: FF solved!

Thanks to Ryan's comment below, Zach and Oleg, turns out there is something that works. And that is:

  1. you create a style element, not a link
  2. add @import "URL"
  3. poll for access to that style node's cssRules collection

It just happens so that Firefox will not populate this collection until the file arrives!

var style = document.createElement('style');
style.textContent = '@import "' + url + '"';

var fi = setInterval(function() {
  try {
    style.sheet.cssRules; // <--- MAGIC: only populated when file is loaded
    CSSDone('listening to @import-ed cssRules');
    clearInterval(fi);
  } catch (e){}
}, 10);  

head.appendChild(style);

Updated my test and seems to work just fine.

Whew!

I still maintain that this is unnecessarily complex and all browsers should simply fire load event. Seems to me this FF behavior might change at any time, as well as Safari's not populating document.styleSheet

One thing to note: this access to cssRules is not a failure of the same security check that doesn't allow cssRules access with outside domains. In this case we're accessing cssRules of the inline style and that's fine. We still cant access the styles of the @import-ed file from a different domain.

Side note: makes me wonder - this could be a technique to preload JS without executing too :)

 

Command-line CSS spriting

Saturday, February 19th, 2011

(In Russian)

OK, CSS sprite tools exist. I'm pretty confident I actually made the very first one :) But they break from time to time (like mine currently). And then the command line is cool (as opposed to scary) and oh-so-quick. And imagemagick is cool and oh-so-powerful. So let's see how we can create CSS sprites from the command line alone.

Creating the image

Starting with a list of separate image files:

$ ls
1.png  2.gif  dot.png  phoney.gif  tw.gif
  • - 1.png
  • - 2.gif
  • - dot.png
  • - phoney.gif
  • - tw.gif

Creating the sprite:

$ convert *png *gif -append result/result-sprite.png

Yes, that's all! The result:

What?

So the imagemagick command is generally something like:

$ convert image1.png image2.png image3.png -append result/result-sprite.png

But we can also replace the list of images with *s:

$ convert * -append result-sprite.png

Or as in the previous case, limiting to *.gif and *.png.

How about a horizontal sprite? All it takes is changing -append to +append:

$ convert *png *gif +append result/result-sprite-horizon.png

The result:

Also note how the source images can be any format - GIF, PNG, JPEG and the result is PNG. Actually I'd recommend always trying PNG8 first:

$ convert *png *gif -append PNG8:result/result-sprite-horizon.png

CSS positions

Now since this is all hand-made there's no auto-generation of CSS. But it's still pretty straightforward. Take the vertical sprite:

All images will have background-position-x of 0px, so that's easy.

The first image will also have Y-position 0px. It also happens to be 16x16 pixels. So it's:

.first {
  width: 16px;
  height: 16px;
  background: url(result/result-sprite.png) 0 0;
}

... where 0 0 position is redundant and can be omitted.

The second image is also 16x16, that's convenient. Its X is 0 and its Y is the height of the previous image (16px) with a minus in front. So:

.secondo {
  width: 16px;
  height: 16px;
  background: url(result/result-sprite.png) 0 -16px;
}

And so on. Y position of an image is Y of the previous + the height of the previous.

You can use the handy-dandy test page to play around with this (or any other) sprite.

But.. but... figuring out dimensions by keeping track of heights? You kiddin' me?

Imagemagick to the rescue. `identify` gives you the basic image info:

$ identify 1.png
1.png PNG 16x16 16x16+0+0 DirectClass 8-bit 260b

`identify` also has a `-format` option and supports *. So getting all the info in a neat form is easy:

$ identify -format "%g - %f\n" *
16x16+0+0 - 1.png
16x16+0+0 - 2.gif
6x6+0+0 - dot.png
10x16+0+0 - phoney.gif
16x16+0+0 - tw.gif

%f is filename and %g is geometry.
\n is a new line as you would expect and sometimes - is just a -.
So if you want to figure out the Y position of the fifth element, well, it's the sum of the heights of the previous: 16+16+6+16

.last {
  width: 16px;
  height: 16px;
  background: url(result-sprite.png) 0 -54px
}

Some complicated math! 'scuse me while I ask my second grader if she can handle it :)

And some smushing

Imagemagick doesn't write optimal PNGs. So some optimization is due. You can do it yourself with pngout, optipng, etc. Or use web-based tools such as smush.it (you're welcome!) or punypng.com. (psst - how bout a glimpse of the past)

Or how about.... smush.it on the command line:

$ curl http://www.smushit.com/ysmush.it/ws.php
       ?img=http://www.phpied.com/files/sprt/result/result-sprite.png

Result is JSON:

{"src":"http:\/\/www.phpied.com\/files\/sprt\/result\/result-sprite.png",
 "src_size":1759,
 "dest":"http:\/\/smushit.zenfs.com\/results\/5a737623\/smush\/%2Ffiles%2Fsprt%2Fresult%2Fresult-sprite.png",
 "dest_size":1052,
 "percent":"40.19",
 "id":""}

Oh looky, almost half the filesize. Let me at it! Copy the `dest` URL:

$ curl http:\/\/smushit.zenfs.com\/results\/5a737623\/
       smush\/%2Ffiles%2Fsprt%2Fresult%2Fresult-sprite.png > result/smushed-sprite.png

And that's that.

Recap

  1. create image:
    $ convert *png *gif -append PNG8:result/result-sprite.png
  2. get dimensions:
    $ identify -format "%g - %f\n" *png *gif
    
  3. optimize:
    $ curl http://www.smushit.com/ysmush.it/ws.php?img=http://url...
    

Test page to play with the result-sprite is here.

For some more ideas and a different imagemagick command for generating sprites - see the very original post announcing the csssprites.com.

 

CSS railroad diagrams

Sunday, November 28th, 2010

So next step after the sexy CSS lexer is parsing. But first - railroad diagrams to help visualize how/when the tokens make sense forming valid CSS code.

Below is what I have so far. It includes pretty much everything except selectors. Selectors are getting increasingly complex, come to think of it.

There are probably mistakes in these diagrams, so I'll be happy to see any cases where they don't work properly.

Here's the list of png images and the powerpoint file I used to make them, in case you want to use (or correct) the diagrams for your own purposes.

Stylesheet

stylesheet css railroad diagram

A stylesheet can have an optional charset, followed by zero or more imports, then zero or more namespace definitions. I struggled a bit whether I want my parser to support namespaces, because I don't think they are widely used, nor exceptionally helpful, but webkit supports them since quite a while, so, heck, let there be namespaces, although they make everything more complex. No wonder JavaScript still doesn't have (or plans for) namespaces.

So after these intro @-rules, there could be any number or combination of rulesets, @font-face, @page, @media and @keyframes, in any order.

@charset

@charset css railroad diagram

Pretty simple, just the literal @charset followed by a string. It has to be the first thing in the CSS file and there can only be one.

E.g.

@charset "UTF-8";

@import

@import css railroad diagram

Any number of imports follow the charset, they can define the URL of the imported CSS as a string or using url(). There's an optional media identifier (in CSS2) turned media query (CSS3). In other words these are all valid:

@import "stuff.css";
@import url(stuff.css);
@import url(stuff.css) print, handheld;
@import url(stuff.css) screen and (color);
@import "stuff.css" screen and (color), projection and (min-color: 256);

Ah, media query syntax, lovely.

@namespace

@namespace css railroad diagram

Namespaces (arrgh!) are innocent-looking to declare:

@namespace svg "http://www.w3.org/2000/svg";
@namespace svg url(http://www.w3.org/2000/svg);
@namespace "http://example.org";
@namespace empty "";
@namespace "";

@keyframe

@keyframe css railroad diagram

Horray for more @-rules. This one is pretty cool actually, has been working well in WebKit (I've used it) via @-webkit-keyframe and allows us to take serious amounts of animation-related code out of JS.

@keyframes 'diagonal-slide' {
  from {
    left: 0;
    top: 0;
  }
  to {
    left: 100px;
    top: 100px;
  }
}

@media

@media css railroad diagram

We've all used stuff like @media print {.banner {display: none}} for quite a while, but things are getting complex these days with the media queries and the ability to nest @page blocks (which can have blocks of their own). Hairier and hairier.

@media screen {
  margin: 20px;
}
@media print and (color), projection and (device-aspect-ratio: 16/9) {
  @page :left {
    @bottom-left-corner {
      margin: 2cm;
    }
  }
}

Media query

media query css railroad diagram

Media queries can get a little scary. In their simplest form they can just be the media type. One of:

aural
braille
handheld
print
projection
screen
tty
tv
embossed
speech
all

(The fact that "all" is also optional makes me think there's a problem with this specific diagram but makes my head hurt. And nose bleed.)

So, simple:

@media print {...}

Or:

@media print, screen, handheld {...}

But you can be more specific, using AND and providing more details about the media type using the so-called media expressions.

@media handheld AND (orientation: portrait) {...}

Then you can negate the whole query with NOT and hide from old browsers with ONLY.

In fact this is pretty popular way to style for iPhone:

@media only screen and (max-device-width: 480px) {
  #bigbanner {
    display: none;
  }
}

You can also keep adding AND expressions and be really picky about the type of device and the features it supports

Media expression

media expression css railroad diagram

The media expressions (not to be confused with IE's expression() value) are wrapped in parentheses and can be:

  • just the feature, e.g. (color)
  • feature plus specific value for it, e.g. (orientation: portrait)
  • min or max value for a feature, e.g. (min-width: 100px). When there's min/max prefix, the value is required

The feature can be one of:

width
height
device-width
device-height
orientation
aspect-ratio
device-aspect-ratio
color
color-index
monochrome
resolution
scan
grid

Those in italics don't support min- and max- prefixes

Ruleset

ruleset css railroad diagram

Whew, finally something that looks normal - the old:

#mydiv {
  color: red
}

Of course, selectors are complicated, I'll deal with them in a later post.

I've moved the property: value declarations into something I called "block" since it occurs not only in rulesets.

Block

block css railroad diagram

Any number of property: value pairs (a.k.a. declarations), delimited by a ; and wrapped in curly braces.

Declaration

declaration css railroad diagram

The plain old property: value pairs.

What the allowed values are, depends on the property. And what the property is, depends on the type of block. Blocks will be reused, for example in @keyframe and @font-face where for example there's a src property, which doesn't make sense in a normal #mydiv {} block.

You can see that I've decided my parser to allow !important as well as !ie. Since IE treats anything after ! as if it was !important, this is a common hack to make something important for IE only. This is the type of thing that I'd like to have that allows real-life validation. And why !ie and not just random !noodles? Well, !ie hints the intent and is more maintainable, where allowing anything including !importent (probably a mistype) is not too validaty and helpful.

@font-face

@font-face css railroad diagram

The @font-face. Nothing overly exciting actually.

@page and page blocks

@page css railroad diagram
page block css railroad diagram

This one can become complex too, but heck, it allows us to style content for book publishing! In a worst case scenario it could have three levels of nested blocks.

The thing is that inside of a normal property: value block there can be nested ruleset-like constructs (only instead of selector, there's a page margin)

@media print {
  @page mine :left {
    margin: 1cm;
    @bottom-left-corner {
      margin: 2cm;
    }
  }
}

/* or something simpler */
@page {
  size: A4 landscape;
}

The page margins allowed are:

@top-left-corner
@top-left
@top-center
@top-right
@top-right-corner
@bottom-left-corner
@bottom-left
@bottom-center
@bottom-right
@bottom-right-corner
@left-top
@left-middle
@right-bottom
@right-top
@right-middle
@right-bottom

The pseudo page values allowed are

:left
:right
:first

Thanks

Did you just scrolled here or you read it all? :)

Thanks for reading and looking forward to any mistake pointers!

 

CSS Lexer

Saturday, November 27th, 2010

I have so much stuff to do and I've been feeling a little overwhelmed lately. Not depressed, because it's next to impossible to be depressed at a climate including 320 sunny days a year and a beach. So I thought why not drop everything and relax. I'm currently staying at home, enjoying my unused vacation days. So no work, no meetings, no nothing. I thought I should relax by taking on a task that requires some degree of concentration as opposed to just jumping from one task to the next.

I have ideas for a bunch of CSS related tools and utilities, all of which, naturally, require understanding of CSS code. So I need a parser and thought I should write one in JavaScript.

The first step is a lexer scanner and I'm happy to share what I committed to github today. It's right here. I called it cssex (yep, cheesy but I didn't want to spend any time thinking of a proper name).

It's not doing much currently but it's a step. It takes a piece of CSS code and tokenizes it producing the following token types:

  • comment
  • string
  • white (spaces or tabs)
  • line (new lines)
  • identifier (could be anything, such as a property or value or font name)
  • number
  • match (5 combinations of two operators used in attribute matching such as ^=)
  • operator - such as . # % * and so on

You can see a test page here and I'll be happy to hear any bug reports. The test page takes CSS, tokenizes it, then recreates the source from the tokens to compare that the original is reproducible. It also highlights the different tokens in different colors and finally dumps the types and values of the tokens (the complete dump is in console.log)

As you can see, I'm continuing with the cheesiness:

  • foreplay.html is the test page (instead of "playground")
  • test-osterone.js (instead of simply "test") is the test runner that uses JavaScriptCore
  • penthouse.sh (instead of "suite") runs tests with the 213 CSS files from CSSZenGarden.com
  • sex.js is the lexer itself which defines the global CSSEX object with two methods - lex(source) and its opposite toSource(tokens)

So what's next is a proper parser validating those tokens produced by the lexer. Then the tools such as a minifier, highlighter, lint, and whatnot (for example something that will add automatically all -moz- and -o- and stuff to your border-radius). But first I need to draw me some railroad diagrams like those Douglas Crockford has for JavaScript and JSON, they should be immensely helpful when parsing. As you can probably guess, Crockford's JSlint and JSON parser and his writeup on Pratt's top down operator precedence is my source of "view source" :)

My main motivation behind all this (other than the itch) is a proper minifier written in JavaScript (therefore running everywhere), not just a collection of regular expressions that YUICSSmin is right now. Also a proper validator, one that understands the nature of the frontend beast and can handle everything from CSS2.1, CSS3's media queries, transitions, latest -webkit and -moz craziness all the way down to IE hacks, expressions, behaviors and filters. And everything in between. Because more often than not we don't validate CSS simply due to the w3c validator being too strict and out of touch with reality.

 

CSS minifiers comparison

Saturday, October 30th, 2010

Last year I compared some CSS minifiers, namely YUICompressor, CSSTidy (with "small" vs. "safe" settings), PHP PEAR's CSS lib and Minify (detailed results). Now that I've done some work on the YUICompressor and since there's a new kid on the block from Microsoft I thought I should give it another go.

I only compared CSSTidy with best compression, YUICompressor and MS' Ajaxmin. The verdict is still the same - CSSTidy looks the best, but there really isn't that big of a difference between the three (and virtually no difference between YUIC and Ajaxmin), especially if you take into account gzipping.

Still on average you get about 35% size reduction when you minify and 80% when you gzip the minified CSS. Meaning when all is done your users only download 19-20% of what they would normally do if you don't perform these simple optimizations.

Experiment

Source CSS files came from csszengarden.com - 213 CSS files.

For the YUICompressor I used the JS verison, on windows with WSH, described in the previous post.

For CSSTidy I used exactly the same code as the last year.

Lastly, Ajaxmin I ran with the default options:

$ AjaxMin.exe in.css -out out.css

Results

CSSTidy wins with bringing the size down to 63.29% of the original on avegare. The other two follow closely with 66.47% (ajaxmin) and 66.41% (YUI). After gzipping: 19.44% (tidy), 19.62% (ajaxmin) and 19.63% (yui). In other words YUI's cssmin performs ever so slightly better that Ajaxmin by 0.06% before gzipping, but after gzipping Ajaxmin wins with 0.01%.

Overall the results show that you shouldn't sweat too much over the choice of CSS minifier. They are all pretty close and you should pick the one that is most convenient for your own use and build process.

CSSTidy and Ajaxmin have parsers, so theoretically they have a much better chance. YUI's cssmin is a bunch of regular expressions and no proper parsing. In reality they are all performing really similarly. Part of the reason is that CSS is not as minifiable as JavaScript as most of it is long selectors and long prperty names (background-image, text-decoration, -webkit-transform-origin, anyone?), which are not renamed to shorter names the way JS variables are.

One random observation (and hint for Ajaxmin folks) is that cssmin strips selectors with empty rulesets while Ajaxmin does not. E.g. #selector {} is totally gone in cssmin but stays in ajaxmin.

Detailed results

File Original cssminjs CSSTidy-small ajaxmin
001.css
gzipped
4832b 100.00%
1781b   36.86%
2650b   54.84%
888b   18.38%
2586b   53.52%
902b   18.67%
2653b   54.90%
892b   18.46%
002.css
gzipped
5242b 100.00%
1936b   36.93%
2651b   50.57%
893b   17.04%
2605b   49.69%
898b   17.13%
2651b   50.57%
893b   17.04%
003.css
gzipped
4964b 100.00%
1856b   37.39%
2734b   55.08%
996b   20.06%
2614b   52.66%
979b   19.72%
2733b   55.06%
992b   19.98%
004.css
gzipped
3854b 100.00%
1285b   33.34%
2370b   61.49%
748b   19.41%
2335b   60.59%
752b   19.51%
2370b   61.49%
748b   19.41%
005.css
gzipped
4648b 100.00%
1616b   34.77%
2652b   57.06%
882b   18.98%
2604b   56.02%
882b   18.98%
2655b   57.12%
884b   19.02%
006.css
gzipped
4319b 100.00%
1435b   33.23%
2689b   62.26%
849b   19.66%
2614b   60.52%
863b   19.98%
2688b   62.24%
850b   19.68%
007.css
gzipped
5412b 100.00%
1557b   28.77%
3694b   68.26%
997b   18.42%
3459b   63.91%
990b   18.29%
3690b   68.18%
993b   18.35%
008.css
gzipped
2143b 100.00%
961b   44.84%
1260b   58.80%
557b   25.99%
1194b   55.72%
553b   25.80%
1260b   58.80%
557b   25.99%
009.css
gzipped
5525b 100.00%
1515b   27.42%
3490b   63.17%
960b   17.38%
3237b   58.59%
946b   17.12%
3490b   63.17%
960b   17.38%
010.css
gzipped
4148b 100.00%
1474b   35.54%
2301b   55.47%
868b   20.93%
2172b   52.36%
853b   20.56%
2307b   55.62%
867b   20.90%
011.css
gzipped
6021b 100.00%
1612b   26.77%
3540b   58.79%
1029b   17.09%
3425b   56.88%
1018b   16.91%
3536b   58.73%
1030b   17.11%
012.css
gzipped
9250b 100.00%
3113b   33.65%
4914b   53.12%
1421b   15.36%
4882b   52.78%
1410b   15.24%
4925b   53.24%
1426b   15.42%
013.css
gzipped
5846b 100.00%
1811b   30.98%
3731b   63.82%
1030b   17.62%
3643b   62.32%
1013b   17.33%
3734b   63.87%
1031b   17.64%
014.css
gzipped
6010b 100.00%
1518b   25.26%
4355b   72.46%
1063b   17.69%
4332b   72.08%
1083b   18.02%
4358b   72.51%
1064b   17.70%
015.css
gzipped
6337b 100.00%
1875b   29.59%
3918b   61.83%
1109b   17.50%
3562b   56.21%
1081b   17.06%
3936b   62.11%
1113b   17.56%
016.css
gzipped
6934b 100.00%
1629b   23.49%
4978b   71.79%
1179b   17.00%
4168b   60.11%
1175b   16.95%
4982b   71.85%
1181b   17.03%
017.css
gzipped
4964b 100.00%
1626b   32.76%
3266b   65.79%
1101b   22.18%
3245b   65.37%
1110b   22.36%
3266b   65.79%
1101b   22.18%
018.css
gzipped
4000b 100.00%
1424b   35.60%
2521b   63.03%
859b   21.48%
2467b   61.68%
862b   21.55%
2519b   62.98%
856b   21.40%
019.css
gzipped
5265b 100.00%
1469b   27.90%
3376b   64.12%
869b   16.51%
3268b   62.07%
879b   16.70%
3406b   64.69%
886b   16.83%
020.css
gzipped
7262b 100.00%
1844b   25.39%
5668b   78.05%
1416b   19.50%
5654b   77.86%
1393b   19.18%
5668b   78.05%
1414b   19.47%
021.css
gzipped
7118b 100.00%
1849b   25.98%
5602b   78.70%
1350b   18.97%
5129b   72.06%
1347b   18.92%
5602b   78.70%
1350b   18.97%
022.css
gzipped
7235b 100.00%
1888b   26.10%
5065b   70.01%
1172b   16.20%
4789b   66.19%
1161b   16.05%
5065b   70.01%
1172b   16.20%
023.css
gzipped
5192b 100.00%
1558b   30.01%
3648b   70.26%
1070b   20.61%
3613b   69.59%
1074b   20.69%
3648b   70.26%
1065b   20.51%
024.css
gzipped
4537b 100.00%
1434b   31.61%
3056b   67.36%
950b   20.94%
2901b   63.94%
936b   20.63%
3062b   67.49%
951b   20.96%
025.css
gzipped
10006b 100.00%
2376b   23.75%
7025b   70.21%
1614b   16.13%
6685b   66.81%
1595b   15.94%
7031b   70.27%
1615b   16.14%
026.css
gzipped
4246b 100.00%
1256b   29.58%
2863b   67.43%
829b   19.52%
2799b   65.92%
826b   19.45%
2863b   67.43%
829b   19.52%
027.css
gzipped
5624b 100.00%
2298b   40.86%
2651b   47.14%
942b   16.75%
2559b   45.50%
941b   16.73%
2651b   47.14%
942b   16.75%
028.css
gzipped
5913b 100.00%
1595b   26.97%
3557b   60.16%
932b   15.76%
2829b   47.84%
849b   14.36%
3563b   60.26%
932b   15.76%
029.css
gzipped
5216b 100.00%
1591b   30.50%
3980b   76.30%
1098b   21.05%
3637b   69.73%
1060b   20.32%
3980b   76.30%
1098b   21.05%
030.css
gzipped
3810b 100.00%
1350b   35.43%
2425b   63.65%
838b   21.99%
2455b   64.44%
839b   22.02%
2423b   63.60%
837b   21.97%
031.css
gzipped
4448b 100.00%
1423b   31.99%
2556b   57.46%
911b   20.48%
2557b   57.49%
905b   20.35%
2556b   57.46%
908b   20.41%
032.css
gzipped
5243b 100.00%
1474b   28.11%
3991b   76.12%
985b   18.79%
3813b   72.73%
992b   18.92%
3984b   75.99%
980b   18.69%
033.css
gzipped
6091b 100.00%
1817b   29.83%
4442b   72.93%
1302b   21.38%
4208b   69.09%
1263b   20.74%
4498b   73.85%
1314b   21.57%
034.css
gzipped
6319b 100.00%
1555b   24.61%
4913b   77.75%
1090b   17.25%
4879b   77.21%
1092b   17.28%
4916b   77.80%
1090b   17.25%
035.css
gzipped
10877b 100.00%
2320b   21.33%
6718b   61.76%
1595b   14.66%
6192b   56.93%
1508b   13.86%
6718b   61.76%
1595b   14.66%
036.css
gzipped
7117b 100.00%
2122b   29.82%
4229b   59.42%
1181b   16.59%
4035b   56.70%
1153b   16.20%
4229b   59.42%
1181b   16.59%
037.css
gzipped
7738b 100.00%
2210b   28.56%
4624b   59.76%
1166b   15.07%
4417b   57.08%
1168b   15.09%
4627b   59.80%
1166b   15.07%
038.css
gzipped
3459b 100.00%
1147b   33.16%
2173b   62.82%
680b   19.66%
2181b   63.05%
681b   19.69%
2173b   62.82%
676b   19.54%
039.css
gzipped
6885b 100.00%
2129b   30.92%
3821b   55.50%
1115b   16.19%
3595b   52.21%
1093b   15.88%
3836b   55.72%
1117b   16.22%
040.css
gzipped
6772b 100.00%
1699b   25.09%
4580b   67.63%
1132b   16.72%
4542b   67.07%
1119b   16.52%
4580b   67.63%
1132b   16.72%
041.css
gzipped
4398b 100.00%
1803b   41.00%
2000b   45.48%
764b   17.37%
1973b   44.86%
754b   17.14%
2002b   45.52%
762b   17.33%
042.css
gzipped
14460b 100.00%
2568b   17.76%
5211b   36.04%
1466b   10.14%
4916b   34.00%
1449b   10.02%
5245b   36.27%
1471b   10.17%
043.css
gzipped
10762b 100.00%
2326b   21.61%
6929b   64.38%
1502b   13.96%
6241b   57.99%
1484b   13.79%
6929b   64.38%
1502b   13.96%
044.css
gzipped
7479b 100.00%
2112b   28.24%
4953b   66.23%
1311b   17.53%
4696b   62.79%
1286b   17.19%
4916b   65.73%
1293b   17.29%
045.css
gzipped
4470b 100.00%
1310b   29.31%
2930b   65.55%
847b   18.95%
2680b   59.96%
841b   18.81%
2933b   65.62%
848b   18.97%
046.css
gzipped
4416b 100.00%
1575b   35.67%
2480b   56.16%
822b   18.61%
2353b   53.28%
817b   18.50%
2480b   56.16%
822b   18.61%
047.css
gzipped
3467b 100.00%
1354b   39.05%
1992b   57.46%
796b   22.96%
2015b   58.12%
799b   23.05%
1992b   57.46%
796b   22.96%
048.css
gzipped
4600b 100.00%
1512b   32.87%
3339b   72.59%
1077b   23.41%
3308b   71.91%
1086b   23.61%
3344b   72.70%
1079b   23.46%
049.css
gzipped
6217b 100.00%
1742b   28.02%
3898b   62.70%
977b   15.71%
3833b   61.65%
977b   15.71%
3901b   62.75%
978b   15.73%
050.css
gzipped
8504b 100.00%
2056b   24.18%
6800b   79.96%
1535b   18.05%
5865b   68.97%
1481b   17.42%
6803b   80.00%
1536b   18.06%
051.css
gzipped
8915b 100.00%
2047b   22.96%
5783b   64.87%
1141b   12.80%
4829b   54.17%
1106b   12.41%
5837b   65.47%
1148b   12.88%
052.css
gzipped
9312b 100.00%
2319b   24.90%
6234b   66.95%
1461b   15.69%
5835b   62.66%
1451b   15.58%
6232b   66.92%
1462b   15.70%
053.css
gzipped
5882b 100.00%
1563b   26.57%
4416b   75.08%
1056b   17.95%
4242b   72.12%
1059b   18.00%
4416b   75.08%
1056b   17.95%
054.css
gzipped
7709b 100.00%
1989b   25.80%
5604b   72.69%
1406b   18.24%
5259b   68.22%
1371b   17.78%
5604b   72.69%
1406b   18.24%
055.css
gzipped
4890b 100.00%
1486b   30.39%
2983b   61.00%
948b   19.39%
2755b   56.34%
919b   18.79%
2983b   61.00%
948b   19.39%
056.css
gzipped
4770b 100.00%
1185b   24.84%
3630b   76.10%
1034b   21.68%
3241b   67.95%
984b   20.63%
3636b   76.23%
1034b   21.68%
057.css
gzipped
12063b 100.00%
3047b   25.26%
10382b   86.06%
2483b   20.58%
9230b   76.51%
2178b   18.06%
9505b   78.79%
2213b   18.35%
058.css
gzipped
8721b 100.00%
1936b   22.20%
6070b   69.60%
1329b   15.24%
5102b   58.50%
1328b   15.23%
6073b   69.64%
1328b   15.23%
059.css
gzipped
6660b 100.00%
1934b   29.04%
4879b   73.26%
1296b   19.46%
4321b   64.88%
1259b   18.90%
4888b   73.39%
1296b   19.46%
060.css
gzipped
4691b 100.00%
1566b   33.38%
2823b   60.18%
922b   19.65%
2629b   56.04%
919b   19.59%
2843b   60.61%
933b   19.89%
061.css
gzipped
4299b 100.00%
1327b   30.87%
2813b   65.43%
850b   19.77%
2803b   65.20%
857b   19.93%
2813b   65.43%
850b   19.77%
062.css
gzipped
15125b 100.00%
2757b   18.23%
11437b   75.62%
2010b   13.29%
10754b   71.10%
1939b   12.82%
11506b   76.07%
2015b   13.32%
063.css
gzipped
6751b 100.00%
2116b   31.34%
4213b   62.41%
1261b   18.68%
4164b   61.68%
1247b   18.47%
4213b   62.41%
1261b   18.68%
064.css
gzipped
5630b 100.00%
1772b   31.47%
4038b   71.72%
1209b   21.47%
3846b   68.31%
1195b   21.23%
4041b   71.78%
1207b   21.44%
065.css
gzipped
5630b 100.00%
1713b   30.43%
4104b   72.90%
1218b   21.63%
3970b   70.52%
1209b   21.47%
4110b   73.00%
1217b   21.62%
066.css
gzipped
5142b 100.00%
1533b   29.81%
3418b   66.47%
938b   18.24%
3088b   60.05%
915b   17.79%
3418b   66.47%
938b   18.24%
067.css
gzipped
4859b 100.00%
1605b   33.03%
3439b   70.78%
1059b   21.79%
3291b   67.73%
1034b   21.28%
3439b   70.78%
1059b   21.79%
068.css
gzipped
9506b 100.00%
2188b   23.02%
4220b   44.39%
1215b   12.78%
4160b   43.76%
1194b   12.56%
4220b   44.39%
1209b   12.72%
069.css
gzipped
4682b 100.00%
1494b   31.91%
3374b   72.06%
1064b   22.73%
3419b   73.02%
1069b   22.83%
3374b   72.06%
1064b   22.73%
070.css
gzipped
5300b 100.00%
1561b   29.45%
3621b   68.32%
1042b   19.66%
3530b   66.60%
1039b   19.60%
3623b   68.36%
1042b   19.66%
071.css
gzipped
4198b 100.00%
1314b   31.30%
2898b   69.03%
897b   21.37%
2752b   65.56%
866b   20.63%
2898b   69.03%
897b   21.37%
072.css
gzipped
5029b 100.00%
1636b   32.53%
3666b   72.90%
1137b   22.61%
3399b   67.59%
1095b   21.77%
3664b   72.86%
1134b   22.55%
073.css
gzipped
3409b 100.00%
1207b   35.41%
2480b   72.75%
838b   24.58%
2417b   70.90%
844b   24.76%
2480b   72.75%
838b   24.58%
074.css
gzipped
5875b 100.00%
1383b   23.54%
3817b   64.97%
879b   14.96%
3347b   56.97%
848b   14.43%
3859b   65.69%
886b   15.08%
075.css
gzipped
6956b 100.00%
1986b   28.55%
3748b   53.88%
1086b   15.61%
3599b   51.74%
1091b   15.68%
3754b   53.97%
1087b   15.63%
076.css
gzipped
6064b 100.00%
1508b   24.87%
4295b   70.83%
1094b   18.04%
4260b   70.25%
1075b   17.73%
4295b   70.83%
1094b   18.04%
077.css
gzipped
6255b 100.00%
1553b   24.83%
4410b   70.50%
1077b   17.22%
3982b   63.66%
1061b   16.96%
4417b   70.62%
1074b   17.17%
078.css
gzipped
6209b 100.00%
1543b   24.85%
3960b   63.78%
945b   15.22%
3543b   57.06%
921b   14.83%
3960b   63.78%
945b   15.22%
079.css
gzipped
2119b 100.00%
1000b   47.19%
1380b   65.13%
627b   29.59%
1399b   66.02%
629b   29.68%
1381b   65.17%
629b   29.68%
080.css
gzipped
5033b 100.00%
1414b   28.09%
3548b   70.49%
957b   19.01%
3351b   66.58%
949b   18.86%
3560b   70.73%
961b   19.09%
081.css
gzipped
8661b 100.00%
1852b   21.38%
6091b   70.33%
1266b   14.62%
5216b   60.22%
1247b   14.40%
6109b   70.53%
1271b   14.67%
082.css
gzipped
5389b 100.00%
1763b   32.71%
3370b   62.53%
1089b   20.21%
3214b   59.64%
1072b   19.89%
3370b   62.53%
1088b   20.19%
083.css
gzipped
6045b 100.00%
1741b   28.80%
4200b   69.48%
1128b   18.66%
4110b   67.99%
1126b   18.63%
4199b   69.46%
1128b   18.66%
084.css
gzipped
4865b 100.00%
1593b   32.74%
3453b   70.98%
921b   18.93%
3273b   67.28%
923b   18.97%
3453b   70.98%
921b   18.93%
085.css
gzipped
4434b 100.00%
1433b   32.32%
3065b   69.12%
956b   21.56%
2843b   64.12%
928b   20.93%
3068b   69.19%
956b   21.56%
086.css
gzipped
7007b 100.00%
1727b   24.65%
2806b   40.05%
947b   13.52%
2792b   39.85%
945b   13.49%
2806b   40.05%
942b   13.44%
087.css
gzipped
7422b 100.00%
1839b   24.78%
3157b   42.54%
996b   13.42%
3158b   42.55%
986b   13.28%
3157b   42.54%
990b   13.34%
088.css
gzipped
13936b 100.00%
3351b   24.05%
7685b   55.14%
1671b   11.99%
6673b   47.88%
1623b   11.65%
7697b   55.23%
1675b   12.02%
089.css
gzipped
4766b 100.00%
1511b   31.70%
3609b   75.72%
1068b   22.41%
3513b   73.71%
1041b   21.84%
3609b   75.72%
1068b   22.41%
090.css
gzipped
5986b 100.00%
1792b   29.94%
4104b   68.56%
1246b   20.82%
3953b   66.04%
1217b   20.33%
4095b   68.41%
1238b   20.68%
091.css
gzipped
3738b 100.00%
1288b   34.46%
2446b   65.44%
902b   24.13%
2399b   64.18%
901b   24.10%
2449b   65.52%
896b   23.97%
092.css
gzipped
4246b 100.00%
1474b   34.72%
2770b   65.24%
997b   23.48%
2761b   65.03%
1001b   23.58%
2818b   66.37%
1001b   23.58%
093.css
gzipped
5169b 100.00%
1572b   30.41%
3376b   65.31%
1012b   19.58%
3081b   59.61%
995b   19.25%
3399b   65.76%
1019b   19.71%
094.css
gzipped
6590b 100.00%
1547b   23.47%
4424b   67.13%
1063b   16.13%
3680b   55.84%
1023b   15.52%
4437b   67.33%
1067b   16.19%
095.css
gzipped
3881b 100.00%
1380b   35.56%
2672b   68.85%
975b   25.12%
2686b   69.21%
972b   25.05%
2672b   68.85%
973b   25.07%
096.css
gzipped
7719b 100.00%
1317b   17.06%
5451b   70.62%
921b   11.93%
3416b   44.25%
833b   10.79%
5451b   70.62%
921b   11.93%
097.css
gzipped
6287b 100.00%
1729b   27.50%
4503b   71.62%
1236b   19.66%
4379b   69.65%
1236b   19.66%
4501b   71.59%
1228b   19.53%
098.css
gzipped
3877b 100.00%
1229b   31.70%
2406b   62.06%
790b   20.38%
2360b   60.87%
800b   20.63%
2406b   62.06%
790b   20.38%
099.css
gzipped
9849b 100.00%
2592b   26.32%
6261b   63.57%
1774b   18.01%
6358b   64.55%
1778b   18.05%
6291b   63.87%
1775b   18.02%
100.css
gzipped
4140b 100.00%
1621b   39.15%
2823b   68.19%
1069b   25.82%
2853b   68.91%
1058b   25.56%
2817b   68.04%
1056b   25.51%
101.css
gzipped
6222b 100.00%
1359b   21.84%
4269b   68.61%
963b   15.48%
4077b   65.53%
953b   15.32%
4264b   68.53%
967b   15.54%
102.css
gzipped
4665b 100.00%
1642b   35.20%
3341b   71.62%
1123b   24.07%
3348b   71.77%
1126b   24.14%
3347b   71.75%
1123b   24.07%
103.css
gzipped
6615b 100.00%
1987b   30.04%
4916b   74.32%
1400b   21.16%
4821b   72.88%
1397b   21.12%
4915b   74.30%
1399b   21.15%
104.css
gzipped
3736b 100.00%
1222b   32.71%
2690b   72.00%
838b   22.43%
2586b   69.22%
827b   22.14%
2690b   72.00%
838b   22.43%
105.css
gzipped
4227b 100.00%
1353b   32.01%
3033b   71.75%
952b   22.52%
2776b   65.67%
947b   22.40%
3040b   71.92%
960b   22.71%
106.css
gzipped
4869b 100.00%
1912b   39.27%
2402b   49.33%
794b   16.31%
2372b   48.72%
800b   16.43%
2402b   49.33%
793b   16.29%
107.css
gzipped
4464b 100.00%
1186b   26.57%
2784b   62.37%
718b   16.08%
2232b   50.00%
705b   15.79%
2886b   64.65%
793b   17.76%
109.css
gzipped
4860b 100.00%
1489b   30.64%
3581b   73.68%
1095b   22.53%
3296b   67.82%
1082b   22.26%
3584b   73.74%
1088b   22.39%
110.css
gzipped
4977b 100.00%
1802b   36.21%
3105b   62.39%
1008b   20.25%
3019b   60.66%
986b   19.81%
3106b   62.41%
1007b   20.23%
111.css
gzipped
6508b 100.00%
1686b   25.91%
4792b   73.63%
1211b   18.61%
4422b   67.95%
1205b   18.52%
4800b   73.76%
1213b   18.64%
112.css
gzipped
4878b 100.00%
1472b   30.18%
3305b   67.75%
965b   19.78%
3240b   66.42%
968b   19.84%
3305b   67.75%
965b   19.78%
113.css
gzipped
4709b 100.00%
1358b   28.84%
3008b   63.88%
850b   18.05%
2730b   57.97%
840b   17.84%
3008b   63.88%
850b   18.05%
114.css
gzipped
4357b 100.00%
1436b   32.96%
2956b   67.84%
966b   22.17%
2862b   65.69%
963b   22.10%
2952b   67.75%
958b   21.99%
115.css
gzipped
8194b 100.00%
1770b   21.60%
5773b   70.45%
1329b   16.22%
5246b   64.02%
1290b   15.74%
5818b   71.00%
1339b   16.34%
116.css
gzipped
4416b 100.00%
1489b   33.72%
2923b   66.19%
969b   21.94%
2800b   63.41%
967b   21.90%
2923b   66.19%
969b   21.94%
117.css
gzipped
6959b 100.00%
1730b   24.86%
4451b   63.96%
1101b   15.82%
3975b   57.12%
1049b   15.07%
4451b   63.96%
1101b   15.82%
118.css
gzipped
7893b 100.00%
2026b   25.67%
5012b   63.50%
1217b   15.42%
4803b   60.85%
1160b   14.70%
5096b   64.56%
1221b   15.47%
119.css
gzipped
5923b 100.00%
1836b   31.00%
4205b   70.99%
1294b   21.85%
4146b   70.00%
1285b   21.70%
4217b   71.20%
1296b   21.88%
120.css
gzipped
4776b 100.00%
1524b   31.91%
2648b   55.44%
867b   18.15%
2549b   53.37%
865b   18.11%
2656b   55.61%
871b   18.24%
121.css
gzipped
4208b 100.00%
1570b   37.31%
2133b   50.69%
809b   19.23%
2144b   50.95%
808b   19.20%
2139b   50.83%
809b   19.23%
122.css
gzipped
7929b 100.00%
2763b   34.85%
4240b   53.47%
1314b   16.57%
4182b   52.74%
1270b   16.02%
4220b   53.22%
1294b   16.32%
123.css
gzipped
6057b 100.00%
1707b   28.18%
4307b   71.11%
1223b   20.19%
4284b   70.73%
1209b   19.96%
4310b   71.16%
1225b   20.22%
124.css
gzipped
5284b 100.00%
1585b   30.00%
3533b   66.86%
1035b   19.59%
3401b   64.36%
1032b   19.53%
3539b   66.98%
1034b   19.57%
125.css
gzipped
4197b 100.00%
1530b   36.45%
2941b   70.07%
1045b   24.90%
2935b   69.93%
1032b   24.59%
2939b   70.03%
1047b   24.95%
126.css
gzipped
4727b 100.00%
1641b   34.72%
3132b   66.26%
1088b   23.02%
2870b   60.72%
1021b   21.60%
3110b   65.79%
1073b   22.70%
127.css
gzipped
5482b 100.00%
1577b   28.77%
3739b   68.21%
1125b   20.52%
3640b   66.40%
1141b   20.81%
3739b   68.21%
1125b   20.52%
128.css
gzipped
4777b 100.00%
1777b   37.20%
2856b   59.79%
984b   20.60%
2838b   59.41%
976b   20.43%
2859b   59.85%
986b   20.64%
130.css
gzipped
5115b 100.00%
1587b   31.03%
3650b   71.36%
1033b   20.20%
3503b   68.48%
1035b   20.23%
3674b   71.83%
1041b   20.35%
131.css
gzipped
4886b 100.00%
1533b   31.38%
3537b   72.39%
1064b   21.78%
3475b   71.12%
1049b   21.47%
3543b   72.51%
1065b   21.80%
132.css
gzipped
3946b 100.00%
1212b   30.71%
2688b   68.12%
805b   20.40%
2660b   67.41%
801b   20.30%
2688b   68.12%
805b   20.40%
133.css
gzipped
4759b 100.00%
1556b   32.70%
3499b   73.52%
1115b   23.43%
3424b   71.95%
1125b   23.64%
3499b   73.52%
1112b   23.37%
134.css
gzipped
4887b 100.00%
1641b   33.58%
3945b   80.72%
1257b   25.72%
3885b   79.50%
1259b   25.76%
3945b   80.72%
1257b   25.72%
135.css
gzipped
5021b 100.00%
1650b   32.86%
3512b   69.95%
1168b   23.26%
3531b   70.32%
1157b   23.04%
3524b   70.19%
1158b   23.06%
136.css
gzipped
4496b 100.00%
1333b   29.65%
2903b   64.57%
841b   18.71%
2797b   62.21%
825b   18.35%
2903b   64.57%
841b   18.71%
137.css
gzipped
6234b 100.00%
1660b   26.63%
4097b   65.72%
1038b   16.65%
3640b   58.39%
1028b   16.49%
4102b   65.80%
1041b   16.70%
138.css
gzipped
5842b 100.00%
1307b   22.37%
4494b   76.93%
913b   15.63%
3129b   53.56%
882b   15.10%
4494b   76.93%
913b   15.63%
139.css
gzipped
3556b 100.00%
1090b   30.65%
2498b   70.25%
669b   18.81%
2254b   63.39%
657b   18.48%
2501b   70.33%
670b   18.84%
140.css
gzipped
5025b 100.00%
1475b   29.35%
3936b   78.33%
1091b   21.71%
3882b   77.25%
1091b   21.71%
3942b   78.45%
1091b   21.71%
141.css
gzipped
3780b 100.00%
1480b   39.15%
2582b   68.31%
977b   25.85%
2446b   64.71%
966b   25.56%
2577b   68.17%
975b   25.79%
142.css
gzipped
4708b 100.00%
1610b   34.20%
3389b   71.98%
1115b   23.68%
3335b   70.84%
1103b   23.43%
3389b   71.98%
1115b   23.68%
143.css
gzipped
4608b 100.00%
1516b   32.90%
3009b   65.30%
1038b   22.53%
3037b   65.91%
1036b   22.48%
3009b   65.30%
1038b   22.53%
144.css
gzipped
4323b 100.00%
1479b   34.21%
2704b   62.55%
945b   21.86%
2705b   62.57%
947b   21.91%
2700b   62.46%
944b   21.84%
145.css
gzipped
4813b 100.00%
1589b   33.01%
3083b   64.06%
1047b   21.75%
2922b   60.71%
1021b   21.21%
3072b   63.83%
1032b   21.44%
146.css
gzipped
4363b 100.00%
1542b   35.34%
3040b   69.68%
960b   22.00%
2782b   63.76%
959b   21.98%
3040b   69.68%
954b   21.87%
147.css
gzipped
5672b 100.00%
1658b   29.23%
4430b   78.10%
1197b   21.10%
3984b   70.24%
1181b   20.82%
4456b   78.56%
1204b   21.23%
148.css
gzipped
9479b 100.00%
2701b   28.49%
5040b   53.17%
1183b   12.48%
4695b   49.53%
1159b   12.23%
5040b   53.17%
1183b   12.48%
149.css
gzipped
5250b 100.00%
1545b   29.43%
3344b   63.70%
1035b   19.71%
3298b   62.82%
1044b   19.89%
3347b   63.75%
1036b   19.73%
150.css
gzipped
6514b 100.00%
1571b   24.12%
4541b   69.71%
1065b   16.35%
4437b   68.11%
1051b   16.13%
4577b   70.26%
1072b   16.46%
151.css
gzipped
5122b 100.00%
1539b   30.05%
3829b   74.76%
1148b   22.41%
3681b   71.87%
1120b   21.87%
3818b   74.54%
1140b   22.26%
152.css
gzipped
4885b 100.00%
1487b   30.44%
3600b   73.69%
1014b   20.76%
3515b   71.95%
1024b   20.96%
3604b   73.78%
1015b   20.78%
153.css
gzipped
4889b 100.00%
1527b   31.23%
3625b   74.15%
1134b   23.19%
3671b   75.09%
1151b   23.54%
3625b   74.15%
1134b   23.19%
154.css
gzipped
5709b 100.00%
1690b   29.60%
4435b   77.68%
1210b   21.19%
3989b   69.87%
1192b   20.88%
4461b   78.14%
1216b   21.30%
155.css
gzipped
3680b 100.00%
1403b   38.13%
2308b   62.72%
829b   22.53%
2307b   62.69%
835b   22.69%
2314b   62.88%
828b   22.50%
156.css
gzipped
4838b 100.00%
1615b   33.38%
3458b   71.48%
1169b   24.16%
3478b   71.89%
1180b   24.39%
3458b   71.48%
1169b   24.16%
157.css
gzipped
3895b 100.00%
1460b   37.48%
2582b   66.29%
795b   20.41%
2464b   63.26%
797b   20.46%
2582b   66.29%
795b   20.41%
158.css
gzipped
4248b 100.00%
1563b   36.79%
2789b   65.65%
1028b   24.20%
2813b   66.22%
1027b   24.18%
2789b   65.65%
1028b   24.20%
159.css
gzipped
5431b 100.00%
1582b   29.13%
3280b   60.39%
1067b   19.65%
3237b   59.60%
1061b   19.54%
3283b   60.45%
1068b   19.66%
160.css
gzipped
4843b 100.00%
1511b   31.20%
3914b   80.82%
1094b   22.59%
3776b   77.97%
1098b   22.67%
3914b   80.82%
1094b   22.59%
161.css
gzipped
4287b 100.00%
1718b   40.07%
2551b   59.51%
1048b   24.45%
2530b   59.02%
1038b   24.21%
2551b   59.51%
1048b   24.45%
162.css
gzipped
7440b 100.00%
2325b   31.25%
4890b   65.73%
1304b   17.53%
4402b   59.17%
1273b   17.11%
4888b   65.70%
1303b   17.51%
163.css
gzipped
6034b 100.00%
1476b   24.46%
4243b   70.32%
1064b   17.63%
3734b   61.88%
1045b   17.32%
4246b   70.37%
1063b   17.62%
164.css
gzipped
3407b 100.00%
1296b   38.04%
2144b   62.93%
761b   22.34%
2023b   59.38%
749b   21.98%
2144b   62.93%
760b   22.31%
165.css
gzipped
4405b 100.00%
1558b   35.37%
3000b   68.10%
1048b   23.79%
3022b   68.60%
1038b   23.56%
3015b   68.44%
1048b   23.79%
166.css
gzipped
6165b 100.00%
1777b   28.82%
4624b   75.00%
1244b   20.18%
4351b   70.58%
1202b   19.50%
4624b   75.00%
1244b   20.18%
167.css
gzipped
5514b 100.00%
1524b   27.64%
3574b   64.82%
1028b   18.64%
3497b   63.42%
1025b   18.59%
3646b   66.12%
1028b   18.64%
168.css
gzipped
3852b 100.00%
1381b   35.85%
2389b   62.02%
812b   21.08%
2407b   62.49%
813b   21.11%
2389b   62.02%
812b   21.08%
169.css
gzipped
6189b 100.00%
1595b   25.77%
4575b   73.92%
1189b   19.21%
4114b   66.47%
1167b   18.86%
4584b   74.07%
1189b   19.21%
170.css
gzipped
4497b 100.00%
1665b   37.02%
3389b   75.36%
1143b   25.42%
3332b   74.09%
1132b   25.17%
3391b   75.41%
1142b   25.39%
171.css
gzipped
4718b 100.00%
1579b   33.47%
2978b   63.12%
920b   19.50%
2828b   59.94%
918b   19.46%
2976b   63.08%
922b   19.54%
172.css
gzipped
7255b 100.00%
1925b   26.53%
4936b   68.04%
1186b   16.35%
4808b   66.27%
1176b   16.21%
4945b   68.16%
1189b   16.39%
173.css
gzipped
3981b 100.00%
1376b   34.56%
3007b   75.53%
955b   23.99%
2983b   74.93%
961b   24.14%
3007b   75.53%
952b   23.91%
174.css
gzipped
4172b 100.00%
1344b   32.21%
3024b   72.48%
953b   22.84%
3012b   72.20%
937b   22.46%
3024b   72.48%
940b   22.53%
175.css
gzipped
4592b 100.00%
1328b   28.92%
2748b   59.84%
909b   19.80%
2638b   57.45%
914b   19.90%
2753b   59.95%
913b   19.88%
176.css
gzipped
3231b 100.00%
1252b   38.75%
2387b   73.88%
854b   26.43%
2362b   73.10%
856b   26.49%
2390b   73.97%
857b   26.52%
177.css
gzipped
6261b 100.00%
1669b   26.66%
3842b   61.36%
1080b   17.25%
3738b   59.70%
1082b   17.28%
3843b   61.38%
1092b   17.44%
178.css
gzipped
5249b 100.00%
1673b   31.87%
3111b   59.27%
1017b   19.38%
2930b   55.82%
1012b   19.28%
3117b   59.38%
1019b   19.41%
179.css
gzipped
5853b 100.00%
1606b   27.44%
3597b   61.46%
1028b   17.56%
3420b   58.43%
1010b   17.26%
3597b   61.46%
1028b   17.56%
180.css
gzipped
4566b 100.00%
1247b   27.31%
2888b   63.25%
793b   17.37%
2230b   48.84%
749b   16.40%
2894b   63.38%
792b   17.35%
181.css
gzipped
4420b 100.00%
1325b   29.98%
3146b   71.18%
951b   21.52%
3002b   67.92%
950b   21.49%
3146b   71.18%
946b   21.40%
182.css
gzipped
4588b 100.00%
1466b   31.95%
3027b   65.98%
900b   19.62%
2906b   63.34%
898b   19.57%
3027b   65.98%
900b   19.62%
184.css
gzipped
5426b 100.00%
1749b   32.23%
4127b   76.06%
1290b   23.77%
4171b   76.87%
1302b   24.00%
4127b   76.06%
1290b   23.77%
185.css
gzipped
4632b 100.00%
1389b   29.99%
3325b   71.78%
952b   20.55%
3326b   71.80%
958b   20.68%
3320b   71.68%
951b   20.53%
186.css
gzipped
3932b 100.00%
1319b   33.55%
2768b   70.40%
935b   23.78%
2736b   69.58%
928b   23.60%
2786b   70.85%
939b   23.88%
187.css
gzipped
5687b 100.00%
1509b   26.53%
3138b   55.18%
936b   16.46%
2879b   50.62%
908b   15.97%
3138b   55.18%
936b   16.46%
188.css
gzipped
5893b 100.00%
1750b   29.70%
3633b   61.65%
1105b   18.75%
3566b   60.51%
1126b   19.11%
3632b   61.63%
1105b   18.75%
189.css
gzipped
6378b 100.00%
1804b   28.28%
3714b   58.23%
1155b   18.11%
3607b   56.55%
1147b   17.98%
3717b   58.28%
1154b   18.09%
190.css
gzipped
4385b 100.00%
1417b   32.31%
2598b   59.25%
920b   20.98%
2443b   55.71%
920b   20.98%
2604b   59.38%
921b   21.00%
191.css
gzipped
5072b 100.00%
1569b   30.93%
3923b   77.35%
1157b   22.81%
3799b   74.90%
1147b   22.61%
3926b   77.41%
1159b   22.85%
192.css
gzipped
5042b 100.00%
1587b   31.48%
3236b   64.18%
1020b   20.23%
3225b   63.96%
1014b   20.11%
3246b   64.38%
1013b   20.09%
193.css
gzipped
4411b 100.00%
1764b   39.99%
2461b   55.79%
872b   19.77%
2485b   56.34%
873b   19.79%
2465b   55.88%
874b   19.81%
194.css
gzipped
6109b 100.00%
1612b   26.39%
4162b   68.13%
1145b   18.74%
4031b   65.98%
1157b   18.94%
4162b   68.13%
1145b   18.74%
195.css
gzipped
4990b 100.00%
1484b   29.74%
3589b   71.92%
1038b   20.80%
3595b   72.04%
1041b   20.86%
3592b   71.98%
1039b   20.82%
196.css
gzipped
4859b 100.00%
1537b   31.63%
3569b   73.45%
1152b   23.71%
3523b   72.50%
1151b   23.69%
3578b   73.64%
1152b   23.71%
197.css
gzipped
6709b 100.00%
1769b   26.37%
4534b   67.58%
1179b   17.57%
4136b   61.65%
1162b   17.32%
4539b   67.66%
1177b   17.54%
198.css
gzipped
4996b 100.00%
1476b   29.54%
3214b   64.33%
975b   19.52%
3078b   61.61%
959b   19.20%
3211b   64.27%
976b   19.54%
199.css
gzipped
4128b 100.00%
1354b   32.80%
2941b   71.25%
914b   22.14%
2752b   66.67%
917b   22.21%
2987b   72.36%
923b   22.36%
200.css
gzipped
5029b 100.00%
1491b   29.65%
3714b   73.85%
1027b   20.42%
3695b   73.47%
1020b   20.28%
3723b   74.03%
1028b   20.44%
201.css
gzipped
5752b 100.00%
1500b   26.08%
3723b   64.73%
973b   16.92%
3463b   60.21%
956b   16.62%
3723b   64.73%
973b   16.92%
202.css
gzipped
5689b 100.00%
1633b   28.70%
4116b   72.35%
1214b   21.34%
3944b   69.33%
1179b   20.72%
4124b   72.49%
1214b   21.34%
203.css
gzipped
3777b 100.00%
1359b   35.98%
2643b   69.98%
978b   25.89%
2649b   70.14%
968b   25.63%
2642b   69.95%
977b   25.87%
204.css
gzipped
11101b 100.00%
1499b   13.50%
4480b   40.36%
1054b   9.49%
4033b   36.33%
1063b   9.58%
4479b   40.35%
1055b   9.50%
205.css
gzipped
4048b 100.00%
1413b   34.91%
2840b   70.16%
967b   23.89%
2862b   70.70%
977b   24.14%
2840b   70.16%
965b   23.84%
206.css
gzipped
4565b 100.00%
1270b   27.82%
3190b   69.88%
883b   19.34%
3083b   67.54%
882b   19.32%
3190b   69.88%
883b   19.34%
207.css
gzipped
3056b 100.00%
1203b   39.37%
2149b   70.32%
798b   26.11%
2113b   69.14%
798b   26.11%
2152b   70.42%
798b   26.11%
208.css
gzipped
4805b 100.00%
1462b   30.43%
3553b   73.94%
1041b   21.66%
3477b   72.36%
1029b   21.42%
3559b   74.07%
1042b   21.69%
209.css
gzipped
7164b 100.00%
1795b   25.06%
4765b   66.51%
1187b   16.57%
4461b   62.27%
1187b   16.57%
4765b   66.51%
1187b   16.57%
210.css
gzipped
6601b 100.00%
1934b   29.30%
4236b   64.17%
1279b   19.38%
4235b   64.16%
1270b   19.24%
4234b   64.14%
1278b   19.36%
211.css
gzipped
5181b 100.00%
1498b   28.91%
3725b   71.90%
1052b   20.30%
3679b   71.01%
1040b   20.07%
3768b   72.73%
1053b   20.32%
212.css
gzipped
5857b 100.00%
2239b   38.23%
4043b   69.03%
1578b   26.94%
4030b   68.81%
1566b   26.74%
4040b   68.98%
1571b   26.82%
213.css
gzipped
7701b 100.00%
1768b   22.96%
5827b   75.67%
1307b   16.97%
5293b   68.73%
1268b   16.47%
5879b   76.34%
1337b   17.36%
Avg size after min 100% 66.41% 63.29% 66.47%
Avg size gzip+min 30.36% 19.63% 19.44% 19.62%
 

cssmin.js in windows shell

Friday, October 29th, 2010

JavaScript can run virtually anywhere, including as a windows exe and the windows command line.

Say you have a JavaScript function foo()

function foo(input) {
  var output = input;
  // .. unicorns
  return output;
}

In order to make this a windows shell script you add at the and a way to read standard input and then write to the standard output:

(function () {

    var input  = WScript.StdIn.ReadAll(),
        output = foo(input);

    WScript.StdOut.Write(output);

}());

Then you run this script, say foo.js, like:

$ cscript foo.js < input.txt

And it prints the output to the console.

If you want to read and print the code of foo.js itself you go:

$ cscript foo.js < foo.js
Microsoft (R) Windows Script Host Version 5.7
Copyright (C) Microsoft Corporation. All rights reserved.

function foo(input) {
  var output = input;
...

You can remove this "Microsoft (R) Windows..." stuff with //NoLogo parameter:

$ cscript //NoLogo foo.js < foo.js
function foo(input) {
  var output = input;
...

CSSMin.JS

Alrighty, going back to the title of the post.

CSSMin.js is a port of YUICompressor's CSS minifier (source, hosted tool). Now adding a few lines at the end makes a windows shell script:

(function () {

    var src = WScript.StdIn.ReadAll();

    if (!src) {
        // help!
        WScript.StdOut.WriteLine("cscript //NoLogo cssmin.js < in.css > out.css");
        return;
    }

    WScript.StdOut.Write(YAHOO.compressor.cssmin(src));

}());

Download it here.

Use it like:

$ cscript //NoLogo cssmin.js < in.css > out.css

Don't forget the //NoLogo or you'll end up with "Microsoft..." in your minified files

Random observation: "dude"[0] === "d" in most JS environments but is undefined in WSH (Windows Scripting Host). So "dude".substring(0, 1)

 

Inline MHTML+Data URIs

Sunday, October 3rd, 2010

MHTML and Data URIs in the same CSS file is totally doable and gives us nice support for IE6+ and all modern browsers. But the question is - what about inline styles. In other words can you have a single-request web application which bundles together markup, inline styles, inline scripts, inline images? With data URIs - yes, clearly. But MHTML?

I remember hacker extraordinaire Hedger Wang coming up with a test page, which proved it's doable. Problems with the test are that a/ I can't find the page anymore, his domain has expired b/ there was some funky IE7/Vista stuff (probably now solvable) in there which even included an undesired redirect c/ was complex - the whole HTML becomes a multipart document, if I remember correctly there was something that required html served as text/plain....

So I tried something simple - shove an MHTML doc inside an inline style comment. It so totally worked! Including IE6 and IE8 in IE7 mode on Windows 7 (which in my experience behaves as badly as IE7 proper on Vista)

Here's the test page. Look ma', no extra HTTP requests :)

So it's a simple HTML doc:

<!doctype html>
<html>
  <head>
    <title>Look Ma' No HTTP requests</title>
    <style type="text/css">

/* magic here */

    </style>
  </head>
  <body>
    <h1>MHTML + Data:URIs inline in a <code>style</code> element</h1>
    <p class="image1">hello<br>hello</p>
    <p class="image2">bonjour<br>bonjour</p>
  </body>
</html>

And the magic is two parts: the MHTML doc inside a CSS comment and the actual CSS which uses data URIs for normal browsers and refers to the MHTML parts in IE6,7.

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

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

iVBORw0KGgoAAAAN ... [more crazyness]... QmCC
--_
Content-Location:polloloco
Content-Transfer-Encoding:base64

iVBORw0KGgoAAAANSUh ... [moarrr] ... ggg==
--_--
*/
.image1 {
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAAN ... QmCC"); 
  *background-image: url(mhtml:http://phpied.com/files/mhtml/mhtml-html.html!locoloco); 
}

.image2 {
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUh ... ggg=="); 
  *background-image: url(mhtml:http://phpied.com/files/mhtml/mhtml-html.html!polloloco); 
}

body {
  font: bold 24px Arial;
}

How cool is that!

Please report any issues you might find in any browser/os combination

The obvious drawback is repeating the long base64'd image content twice, but it's solvable with either server-side sniffing or... one crazy hack, found on the Russian site habrahabr.ru. I should talk about it separately and help spread the word to the larger English-speaking audience, but for the impatient - click!

So there you go - MHTML inline in CSS inline in HTML or building single-request x-browser web apps :)

 

The proper MHTML syntax

Sunday, October 3rd, 2010

Reducing the number of HTTP requests is a must, sprites are cool, but a pain to maintain, so there come data URIs (for all browsers) and MHTML (IE6 and 7). I've talked about these things on this blog to a point where the blog comes up in top 10 results in search engines for queries like "mhtml" and "data url". Therefore I think it's my duty to clarify a point for the good of the mankind :)

MHTML works in IE6 and IE7 even in the deadly IE7/Vista and IE7/Win7 combos

In the community we've long considered MHTML in IE7/Vista a problem and I've personally come up with complex voodoo workarounds how to mitigate the issue and still make use of the technique. Turns out the whole problem all the time was a small syntax glitch.

Pointed by a comment at a previous post all we ever needed was to close the boundary delimiter and add two dashes at the end. The double-dash of doom as I like to call them since I've spent so much time wrestling.

So let's take a look at the syntax.

Update: Also check this comment for additional insight from Vincent, Aaron and _cphr_ regarding double line break of doom

One part

MHTML is a multi-part document. One document containing several parts. One part looks like this:

Content-Location: myimage
Content-Transfer-Encoding: base64

iVBORw0KGgoAAAANSU....U5ErkJggg==

In other words it has headers, base64-encoded content and two empty lines to divide them.

Multi parts

The different parts in the document are divided by a separator string. And at the top of the document you define what this separator is. Anything you like. So

Content-Type: multipart/related; boundary="MYSEPARATOR"

--MYSEPARATOR

[here comes part one]

--MYSEPARATOR

[here's part two]

--MYSEPARATOR--

Did you notice -- at the very end? Yes, this is the double-dash of doom. Forget it and you get IE7/Vista problems (only on cached documents) and permanent hair loss. The thing is that in IE6 and other IE7s you can omit the whole last separator and it's all good. So historically you never needed it, but come Vista and Win7 and problems start.

All together now

Finally, let's see the whole thing, a whole CSS file, including the way you refer to the parts later on in the CSS.

/*
Content-Type: multipart/related; boundary="MYSEPARATOR"
 
--MYSEPARATOR
Content-Location: myimage
Content-Transfer-Encoding: base64
 
iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAD....U5ErkJggg==

--MYSEPARATOR
Content-Location: another
Content-Transfer-Encoding: base64
 
iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAA....U5ErkJggg==

--MYSEPARATOR--
*/
.myclass {
    background-image:url(mhtml:http://example.org/styles.css!myimage);
}
.myotherclass {
    background-image:url(mhtml:http://example.org/styles.css!another);
}

Updated PHP class

Previously when I fought IE7/Vista I came up with a PHP class that would take some images and create "data sprites" on the fly, creating two different versions - one with data URIs and one with MHTML, depending on the browser. The old code is here.

Now, I've updated it (basically just deleted serious portions of it that dealt with Vista) and put it up on github. Right here.

Updated test pages

Thanks for reading, that's about it. Now off I go to correct the older posts, catch ya later :)

 

Conditional comments block downloads

Sunday, May 23rd, 2010

I came across this blog post (via @pornelski and @souders) where Markus has stumbled upon a case where an IE6-only stylesheet included with a conditional comment blocks the downloads in IE8. Whaaat?

I had to dig in. To give you a summary: turned out that any conditional comment, not only for an extra CSS, will block further downloads until the main CSS file arrives. Also the solution offered on the blog post (using X-UA-Compatible) seems to be more of an error due to an accidentally left comment.

Check out the tests.

Base page

The first test is the base page. It follows a pretty common style pattern - CSS at the top, a bunch of images in the middle, JS at the bottom.

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

<html>
<head>
    <title>The base page</title>
    <link type="text/css" rel="stylesheet"
          href="http://tools.w3clubs.com/pagr/1.expires.css">
</head>
<body>
<p>
    <img src="http://tools.w3clubs.com/pagr/1.expires.png" alt="1">
    <img src="http://tools.w3clubs.com/pagr/2.expires.png" alt="2">
    <img src="http://tools.w3clubs.com/pagr/3.expires.png" alt="3">
    <img src="http://tools.w3clubs.com/pagr/4.expires.png" alt="4">
</p>
<script type="text/javascript"
        src="http://tools.w3clubs.com/pagr/1.expires.js"></script>
</body>
</html>

The waterfall produced by WebPageTest looks like so:

base page

The page is here, the results of the WebPageTest's test are here.

Conditional IE6 stylesheet

Adding a second stylsheet to the head like so:

<head>
  <title>base page</title>
  <link type="text/css" rel="stylesheet"
        href="http://tools.w3clubs.com/pagr/1.expires.css">
  <!--[if IE 6 ]>
    <link type="text/css" rel="stylesheet"
          href="http://tools.w3clubs.com/pagr/2.expires.css">
  <![endif]-->
</head>

Turns out that this conditional comment blocks further downloads until the main CSS arrives.

Test page, test results, waterfall:

CC style page

Just like that the total page to onload time went up from 1 second to almost 1.3 seconds. Ouch.

And this is because of an IE6 stylesheet, which IE8 has no use for. My wild guess is that IE needs to parse through those conditional comments and treats them sort of like inline script. And we know that inline scripts following a stylesheet tend to block.

Conditional markup

What if we don't include IE6 specific stylsheet, but use the conditional comments to write different body tags with different class names, as described by Paul Irish.

<!--[if IE 6]> <body class="ie6"> <![endif]-->
<!--[if !IE]><!--> <body> <!--<![endif]-->

Turns out that these markup conditional comments will also block downloads until the CSS arrives.

Test page, test results, waterfall looks as bad (the same blocking).

Browser-sniffing comments

I blogged about this a few days ago - using conditional comments to do the browser sniffing and include appropriate CSS - one for normal browsers and a complete alternative for IE6,7.

<head>
  <!--[if lte IE 7]>
    <link type="text/css" rel="stylesheet"
          href="http://tools.w3clubs.com/pagr/2.expires.css">
  <![endif]-->
  <!--[if gt IE 7]><!-->
    <link type="text/css" rel="stylesheet"
          href="http://tools.w3clubs.com/pagr/1.expires.css">
  <!--<![endif]-->
</head>

Turns out this is OK to do. The conditional comments are processed before the download is initiated, so there't nothing to block on after the stylesheet. Yeey!

Test page, test results, waterfall looks like the first one for the base page.

X-UA-Compatible not a solution

The blog post suggested using the X-UA-Compatible meta tag to say that the UA is the latest IE possible.

<meta http-equiv="X-UA-Compatible" content="IE=edge">

It didn't work for me.

Test page, test results, waterfall like the second (the blocking) one.

Looking closely and thanks to the screenshots digging out the original test page I noticed that it contains a dangling comment. Putting that dangling comment in a test page, and the blocking effect was gone. But this is a bug. In fact the comment shows up on the page! My wild guess is that this improper comments invalidates the following one and that's why there's no blocking. I guess that IE6 will not load the stylesheet, but I didn't test.

So my tests - with X-UA meta tag (results) and with comment bug (results)

Conclusions, conclusions

To summarize, if you worry about performance, don't use conditional comments.

There might be an exception when you put a script in the head after the CSS - then there are two blocking things, so the effect of the conditional comment is not visible. But that's still bad for performance, there's still blocking.

It's OK to use the browsers-sniffing comments approach, provided of course, that there's no other CSS file before the sniff. If there is one, it will block.

Best - just use _ and * hacks, go with a single CSS and forget sniffing.

Update: empty conditional comment

Thanks to Markus who kept looking into the extra conditional comment comes a solution: an empty conditional comment early on solves the blocking issue.. By "early on" I mean before the main CSS which caused the blocking.

I did two more tests to validate the solution and it absolutely works.

One test has empty comment + conditional comment for writing Paul's body tags. The empty comment (aka the solution) is right before the blocking CSS file.

<head>
    <title>base page</title>
    <!--[if IE 6]><![endif]-->
    <link type="text/css" rel="stylesheet"
          href="http://tools.w3clubs.com/pagr/1.expires.css">
</head>

<!--[if IE 6]> <body class="ie6"> <![endif]-->
<!--[if !IE]><!--> <body> <!--<![endif]-->

Test page, webpagetest results. Works as advertised, no more blocking.

The second test uses empty comment and conditional stylesheet. In this case I even put the empty comment way at the top. Sort of like declaring upfront - hey this page uses conditional comments and the empty comment is the solution to the blocking effect.

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
   "http://www.w3.org/TR/html4/strict.dtd">
<!--[if IE 6]><![endif]-->

<html>
<head>
  <title>base page</title>
  <link type="text/css" rel="stylesheet"
        href="http://tools.w3clubs.com/pagr/1.expires.css">
  <!--[if IE 6 ]>
    <link type="text/css" rel="stylesheet"
          href="http://tools.w3clubs.com/pagr/2.expires.css">
  <![endif]-->
</head>

Test page, webpagetest results. No more blocking :)

Answering Andrea's question - what it if you have several conditionals - for IE6, IE7 and so on, I did one more test with two conditional CSS files. Turns out it's fine, as long as there's one conditional comment before the CSS. I actually updated the comment so it checks for all IE versions.

Test page, results, code:

<!--[if IE]><![endif]-->
<html lang="en">
<head>
    <title>base page</title>
    <link type="text/css" rel="stylesheet"
          href="http://tools.w3clubs.com/pagr/1.expires.css">
    <!--[if IE 6]>
        <link type="text/css" rel="stylesheet"
              href="http://tools.w3clubs.com/pagr/2.expires.css">
    <![endif]-->
    <!--[if IE 7]>
        <link type="text/css" rel="stylesheet"
              href="http://tools.w3clubs.com/pagr/3.expires.css">
    <![endif]-->
</head>

In conclusion #2... conditional comments cause CSS to block. The workaround it to have an extra empty comment before the blocking CSS, or, to be safe, right after the doctype. Even better - don't use conditional comments at all. Except for browser-sniffing and loading two complete and completely separate CSS files, not just the IE fixes.

 

YUI CSS min – part 3 – hacks

Friday, May 21st, 2010

The previous parts are here (building and testing) and here (what gets minified). Now let's see how YUI CSS min handles CSS hacks.

As you know CSS hacks often use errors in CSS parsers in browsers to target specific browser versions and supply additional rules to work around other issues in said browsers. That makes any CSS tool's job slightly more challenging. Not only does the tool have to avoid repeating the browsers errors, but also has to understand what browsers got wrong and support it too. Fun stuff. Isn't it a joy being a web developer?

So here are some hacks that are tested to work with the YUICopmpressor's CSS min.

Underscore/star hack

The simplest ever hack to target IE6 and IE7. In the example below normal browsers see 1px dropping _width and *width as invalid, IE7 ignores the *, drops the _width as invalid and sees 3pt, IE6 ignores the _ and sees _width as width, so it sees 2em.

CSS min doesn't parse and doesn't understand CSS properties, so it accepts pretty much any property.

Before:

#element {
    width: 1px;
    *width: 3pt;
    _width: 2em;
}

After:

#element{width:1px;*width:3pt;_width:2em}

Child selector hack

CSS min strips comments but there is this child selector hack people use to hide declarations from IE7 and below.

CSS min retains empty comments that immediately follow > (thanks go out to Chris Burroughs)

Before:

html >/**/ body p {
    color: blue;
}

After:

html>/**/body p{color:blue}

IE5/Mac hack

This hack targets IE5/Mac, if anyone still worries about this browser. The hack is retained after minification, only it's minified.

Before:

/* Ignore the next rule in IE mac \*/
.selector {
   color: khaki;
}
/* Stop ignoring in IE mac */

After:

/*\*/.selector{color:khaki}/**/

Box model hack

This hack uses valid CSS and there's no special use of comments so it's retained.

Before:

#elem {
    width: 100px; /* IE */
    voice-family: "\"}\"";
    voice-family:inherit;
    width: 200px; /* others */
}
html>body #elem {
    width: 200px; /* others */
}

After:

#elem{width:100px;voice-family:"\"}\"";voice-family:inherit;width:200px}html>body #elem{width:200px}

Seems like the code highlighter chokes here though. It ain't easy :)

That's all, folks!

Thanks and please, feel free to suggest improvements and report bugs. Also play with the web UI of the JS-version here to see for yourself what it does to your code.

 

YUI CSS Min – part 2

Thursday, May 20th, 2010

The first part is here. It was more about building the YUICompressor, writing and running test cases. Now let's see what the compressor does exactly to your CSS.

BTW, you can play with the web UI to see for yourself how the minifier works.

Stripping comments and white space

This is the bare minimum a minifier can do. And when it comes to CSS, this is also the place where ther biggest improvement comes from. In JS for example you can rename variables and save bytes, but in CSS the possibilities are more limited. No shorter way to say text-decoration, unfortunately.

So before:

/*****
  classmates stuff
*****/
.classmates {
    /* after 10 years */
    weight: considerable;
}

After:

.classmates{weight:considerable}

Special comments

Stripping comments is nice but not always ok. Sometimes you need to retain copyright information. Use ! at the beginning of the comment to mark the comment as special.

Before:

/*!
  (c) copyright copyleft
*/
.classmates {
    /* after 10 years */
    weight: considerable;
}

After:

/*!
  (c) copyright copyleft
*/.classmates{weight:considerable}

Thanks to the charmingly insisting Billy Hoffman and the valid case he presented, the bang (!) itself is preserved too. This way you can safely double minify. Also lint tools (such as Zoompf, YSlow and PageSpeed) can see the ! and conclude that this comment is there intentionally, not because you forgot to minify.

Striping last semi-colon

The last semi-colon in a declaration block is out. So keep it in your source for maintenance purposes and let the minifier take care of stripping it out.

Before:

a {
  one: 1;
  two: 2;
}

After:

a{one:1;two:2}

Extra semi-colons

One semi-colon is all you need, so the minifier will strip an accidentally added one.

Before:

p :link {
  ba: zinga;;;
  foo: bar;;;
}

After:

p :link{ba:zinga;foo:bar}

No empty declarations

Empty declaration blocks don't do anything, so why send them over the net?

Before:

.empty { ;}

After:

(nothing...)

Zero values

A zero is a zero. Zero pixels or % or centimeters or whatever, it's still zero. Also sometimes (when everything is a zero) you need just one zero instead of four, or three or two.

Before:

a {
  margin: 0px 0pt 0em 0%;
  background-position: 0 0ex;
  padding: 0in 0cm 0mm 0pc
}

After:

a{margin:0;background-position:0 0;padding:0}

Floats

For values such as 0.something, the 0 is not needed.

Before:

::selection {
  margin: 0.6px 0.333pt 1.2em 8.8cm;
}

After:

::selection{margin:.6px .333pt 1.2em 8.8cm}

Colors values

RGB color values are nice, but not the most concise form. Make them hex. Also AABBCC hex can be the shorter ABC. But don't touch RGBA and don't touch the IE filter values in quotes.

Before:

.color {
  me: rgb(123, 123, 123);
  impressed: #ffeedd;
  background: none repeat scroll 0 0 rgb(255, 0,0);
}

After:

.color{me:#7b7b7b;impressed:#fed;background:none repeat scroll 0 0 #f00}

Before:

.cantouch {
  alpha: rgba(1, 2, 3, 4);
  filter: chroma(color="#FFFFFF");
}

After (no color minification) :

.cantouch{alpha:rgba(1,2,3,4);filter:chroma(color="#FFFFFF")}

Single charsets

Only one charset is allowed per stylesheet. So, if there's more than one, strip it. It may happen when merging several stylesheets into one.

Before:

@charset "utf-8";
#foo {
  border-width: 1px;
}

/* second css, merged */
@charset "another one";
#bar {
  border-width: 10px;
}

After:

@charset "utf-8";#foo{border-width:1px}#bar{border-width:10px}

Alpha opacity

There's a shorter way to write opacity filter for IE.

So before:

code {
   -ms-filter: "PROGID:DXImageTransform.Microsoft.Alpha(Opacity=80)"; /* IE 8 */
   filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80);       /* IE 4-7 */
}

After:

code{-ms-filter:"alpha(opacity=80)";filter:alpha(opacity=80)}

There are more filters that could be shorten besides the opacity, but MSDN suggests the longer syntax should be used, So a bit more experimentation is needed here...

Thanks!

Whew, sort of a lengthy post. Thank you for reading and coming up next time... hacks :)

If you have ideas, comments, your bug reports are welcome

 

15 minutes could save you…

Monday, May 17th, 2010

Since I have a ton of things to do, I decided it was about time to spend some time with this blog, performance optimization-wise. Not do anything special, just the bare minimum, the no-brainer, works-every-time, easy stuff. And I'm quite happy with the results.

I only looked at the homepage, although the results will be seen throughout the site. Unfortunately there was a youtube video on the homepage, otherwise the results would have been even better, KB-wise

gzip on

First things first - turning compression on. I've previously whined about the host of this blog, site5.com and how I wasn't able to enable compression for some tests and had to make PHP do the compression. Turned out, all it takes is just ask and open a support ticket. Second level support decided to compile Apache with mod_deflate and I was good to go. I put this in .htaccess:

AddOutputFilterByType DEFLATE text/css text/plain text/xml application/javascript application/json

This blog has no JavaScript, just HTML and CSS. The HTML became 5K (from 23K) and CSS became 3 something (from 11K)

Flush

Decided to go fancy here and do first byte flush early on. That, of course, can be problematic, especially on shared hosting. I mean problematic is to have gzip working together with flushing (tips). Eventually I gave up wrestling with .htaccess and php.ini settings and let PHP handle it. That's why you may notice that text/html is missing from the DEFLATE list above.

So all it took was going to my WordPress theme, finding something called header.php and adding two lines of PHP.

One at the top:

<?php ob_start("ob_gzhandler"); ?>
<!DOCTYPE html ...

And one at the bottom:

<?php ob_flush(); flush(); ?>

Now the header part is flushed in one chunk and the rest in another. Check it in chunkview...

favicon.ico

I have no favicon so I get a lot of 404s, since browsers insist on downloading this little thing. So I created one. Took a social profile photo, croped (Option+K) and played with green colors in Mac's built-in Preview program. Then, ImageMagick:

$ convert -resize 16x16 dude.png PNG8:favicon16.png
$ convert favicon16.png favicon.ico

FTP. Done. No mo' 404s for favicon.

Minifying CSS

Copy-paste into CSSMin and CSS lost 30% of its weight. Now after gzipping and minifying, the CSS went from total 11.3K to 2.6K.

Cover images

I have some book covers on the homepage. One was simply linked to the publisher's site (extra DNS, connection...). Also turned out the publisher has redesigned the site so there was also a redirect. Disaster. Also the image was a 26K PNG where it could be 4k JPEG.

$ convert cover.png cover.png.jpg
$ jpegtran -copy none -optimize cover.png.jpg > cover.jpg

And just like that - 4 book covers were optimized.

Before/after

And that's all I did. I can probably do Expries headers too, convert/sprite all smiley GIFs and so on but that means touching more of WordPress, so I stopped here.

Now the page loads (onload) in 1.2 seconds, down from 2.2 (45% faster).

There are fewer DNS lookups, no 404s, no 301s

Page weight is down from 285K to 186K (34%). Actually if you exclude the youtube 142K SWF, the result is: 143K (before) to 44K (after) or a page weight saving of 70%. Not bad, not bad at all.

And the waterfalls. Before:

After:

PageSpeed score only went from 84/100 to 87/100 which was not impressive at all. I think 84K may have been too generous, but maybe not, given how worse sites there are out there.

So this is it - 15 minutes could save you 45% page load time and 70% download sizes :)

 

Browser sniffing with conditional comments

Thursday, May 13th, 2010

Browser sniffing is bad. But sometimes unavoidable. But doing it on the server is bad, because UA string is unreliable. The solution is to use conditional comments and let IE do the work. Because you're targeting IE most of the times anyway.

In fact IE8 is a decent browser for the most practical purposes and often you're just targeting IE before 8.

Conditional comments in practice use the following pattern:

  1. Load the decent browsers CSS
  2. Conditionally load IE6,7 overrides

The drawback is that IE6,7 get two HTTP requests. That's not good. Another drawback is that having a separate IE-overrides stylesheet is an excuse to get lazy and instead of solving a problem in a creative way, you (and the team) will just keep adding to it.

We can avoid the extra HTTP request by creating our CSS bundles on the server side and having two browser-specific but complete stylesheet files:

  1. The decent browsers CSS
  2. The complete CSS for IE6,7 not only the overrides

Then the question is loading one of the two conditionally without server-side UA sniffing. The trick (courtesy of duris.ru) is to use conditional comments to comment out the decent CSS so it's not loaded at all:

<!--[if lte IE 7]>
  <link href="IE67.css" rel="stylesheet" type="text/css" />
<![endif]-->
<!--[if gt IE 7]><!-->
  <link href="decent-browsers.css" rel="stylesheet" type="text/css" />
<!--<![endif]-->

The highlighting suggests what the decent browsers see.

IE6,7 see something like this after the conditional comments are processed:

  <link href="IE67.css" rel="stylesheet" type="text/css" />
<!--
  <link href="decent-browsers.css" rel="stylesheet" type="text/css" />
-->
 

Preload CSS/JavaScript without execution

Wednesday, April 21st, 2010

Preloading components in advance is good for performance. There are several ways to do it. But even the cleanest solution (open up an iframe and go crazy there) comes at a price - the price of the iframe and the price of parsing and executing the preloaded CSS and JavaScript. There's also a relatively high risk of potential JavaScript errors if the script you preload assumes it's loaded in a page different than the one that preloads.

After a bit of trial and lot of error I think I came up with something that could work cross-browser:

  • in IE use new Image().src to preload all component types
  • in all other browsers use a dynamic <object> tag

Code and demo

Here's the final solution, below are some details.

In this example I assume the page prefetches after onload some components that will be needed by the next page. The components are a CSS, a JS and a PNG (sprite).

window.onload = function () {

    var i = 0,
        max = 0,
        o = null,

        // list of stuff to preload
        preload = [
            'http://tools.w3clubs.com/pagr2/<?php echo $id; ?>.sleep.expires.png',
            'http://tools.w3clubs.com/pagr2/<?php echo $id; ?>.sleep.expires.js',
            'http://tools.w3clubs.com/pagr2/<?php echo $id; ?>.sleep.expires.css'
        ],
        isIE = navigator.appName.indexOf('Microsoft') === 0;

    for (i = 0, max = preload.length; i < max; i += 1) {

        if (isIE) {
            new Image().src = preload[i];
            continue;
        }
        o = document.createElement('object');
        o.data = preload[i];

        // IE stuff, otherwise 0x0 is OK
        //o.width = 1;
        //o.height = 1;
        //o.style.visibility = "hidden";
        //o.type = "text/plain"; // IE 
        o.width  = 0;
        o.height = 0;

        // only FF appends to the head
        // all others require body
        document.body.appendChild(o);
    }

};

A demo is here:
http://phpied.com/files/object-prefetch/page1.php?id=1
In the demo the components are delayed with 1 second each and sent with Expries header. Feel free to increment the ID for a new test with uncached components.

Tested in FF3.6, O10, Safari 4, Chrome 5, IE 6,7,8.

Comments

  • new Image().src doesn't do the job in FF because it has a separate cache for images. Didn't seem to work in Safari either where CSS and JS were requested on the second page where they sould've been cached
  • the dynamic object element has to be outside the head in most browsers in order to fire off the downloads
  • dynamic object works also in IE7,8 with a few tweaks (commented out in the code above) but not in IE6. In a separate tests I've also found the object element to be expensive in IE in general.

That's about it. Below are some unsuccessful attempts I tried which failed for various reasons in different browsers.

Other unsuccessful attempts

1.
I was actually inspired by this post by Ben Cherry where he loads CSS and JS in a print stylesheet. Clever hack, unfortunately didn't work in Chrome which caches the JS but doesn't execute it on the next page.

2.
One of the comments on Ben's post suggested (Philip and Dejan said the same) using invalid type attribute to prevent execution, e.g. text/cache.

var s = document.createElement('script');
s.src = preload[1];
s.type = "text/cache";
document.getElementsByTagName('head')[0].appendChild(s);

That worked for the most parts but not in FF3.6 where the JavaScript was never requested.

3.
A dynamic link prefetch didn't do anything, not even in FF which is probably the only browser that supports this.

for (i = 0, max = preload.length; i < max; i += 1) {
    var link = document.createElement('link');
    link.href = preload[i];
    link.rel = "prefetch";
    document.getElementsByTagName('head')[0].appendChild(link);
}

Then it took a bit of trial/error to make IE7,8 work with an object tag, before I stumbled into IE6 and gave up in favor of image src.

In conclusion

I believe this is a solution I could be comfortable with, although it involves user agent sniffing. It certainly looks less hacky than loading JS as CSS anyways. And object elements are meant to load any type of component so no semantic conflict here I don't believe. Feel free to test and report any edge cases or browser/OS combos. (JS errors in IE on the second page are ok, because I'm using console.log in the preloaded javascript)

Thanks for reading!

 

Publishing 5 books this year

Thursday, April 1st, 2010

So I'll be publishing 5 books this year. Isn't that incredible? Is it even possible? And good quality books at that? It's a nice challenge (my last year's challenge failed, I didn't even bother to count how bad it failed). I think it's possible, especially if you bend a little bit the meaning of "5", "year", "publishing" and "me" :)

Book #1 - High-Performance JavaScript

hpjs

Let's start bending - this is a book where I wrote just one chapter. It's a book by Nicholas Zakas with contributions from:

And I wrote my chapter mainly the last year. My chapter is about the DOM. But the book became available just now, few days ago, so it's published this year (bending, bending...)

Book #2 - JavaScript Patterns

I am hard at work on this one currently (explains the low activity on this blog). I started last year but only finished two chapters in '09. The bending part here is that I've already given presentations on the topic and have been writing a "patterns" column for JSMag for a while, so I can recycle quite a bit of content.

You can see the tentative cover, I hope it stays tentative and we can replace the hen with a nice cute little zebra (a.k.a. donkey with patterns). Between you and me, I think there's a new designer in O'Reilly with a bird fetish.

I expect the first draft for this one to complete within weeks. And no, it's not about implementing the Gang of Four patterns in JavaScript (has been done already by Ross, see above), although there's one chapter on a selected few - Singleton, Factory, Observer, Proxy, Decorator...

Book #3 - Speed Matters

I've contracted with Peachpit Press to write a book about performance targeted mainly at designers. It will be about the business (why go fast), technology (how) and psychology (perception of speed) of web performance. I'm excited about this one for a number of reasons:

  • there's a lot of misconceptions being spread around in designer blogs and books, especially sad when one of the books in question is a sort of a bible for web designers. I mean things like PNG vs. GIF, gzipping and others. I hope I can present a readable, concise and, above all, technically correct text for designers who may find Steve Souders' HPWS, a.k.a. "The Bible" a little too dry because it's from O'Reilly and has no colors
  • the publisher is considering a sort of novel approach to writing the book, fingers crossed, because I believe it's the right way to write technical books.
  • at the very least, the book will be available as early drafts while it's being written, which is new to me, but always wanted to do.
  • the book will be full color - again, new experience to me

The bending here comes from the fact that I'll try to reuse from the perf advent calendar if I can. So some content may be pre-written.

Book #4 - Object-Oriented JavaScript (2nd edition)

The bending here is obvious - it's just a second edition, not a completely new book from scratch. My goal here is:

  • address errata
  • address some excellent critiques (of this otherwise bestselling book!), such as this one by @kangax, which is the article that actually prompted me to pitch a second edition to the publisher. So many thanks to Yuri! Also thanks to Asen who's been sending me invaluable and detailed feedback on the first edition. And now thanks to Asen and Kangax (and also Dmitry) I'm spending some time lurking on comp.lang.javascript mailing list, which is full of great discussions.
  • ECMAScript5 update
  • some concepts such as hoisting, NFE, property attributes, etc
  • one completely new chapter on testing and docs
  • answers to the end-of-chapter exercises - an often-requested update

Hoping this title will not take a lot of time.

And since these 4 books should be finished by the end of August or thereabouts, this will give me whole 4 months (1/3 of an year) to dive into something I've been thinking about, two things actually - CSS and self-publishing.

Book #5 - CSS for web devs

CSS is widely misunderstood by many people, me including. I'm convinced we only use a portion of all that CSS is, and use it badly. I'm not saying it will be CSS: The Good Parts, but I plan to address what I consider bad habits in CSS (mis)use and write a book as a learning experience. This is the best way to learn IMO. It will be self-published and probably available online for free too. And by self-publish I don't mean lulu.com or some of the other resellers, but working with the printer and distributor directly.

Too ambitious? April Fool's?

Probably, but with all the pre-written stuff and other cheating, it may very well be doable. Then I guess I'll take a 5 year break :)

 

YUICompressor’s CSSMin

Wednesday, March 10th, 2010

Honored to be a part of the YUI project, I am now helping with the maintenance of the CSSMin part of the YUICompressor. My changes are now part of the trunk on github, so I'm official. Next on the agenda is documenting the thing, so that's what I'll try to do here, maybe in a few posts. You know, divide and conquer.

PHP, Java and a JavaScript port

Originally written in PHP by Isaac Schlueter and ported to Java by Julien Lecomte, CSSMin got a JavaScript port by yours truly some time ago. Because, after all, JavaScript is the language of the web, isn't it?

You can play with the latest git version of the JS port online here.

I'm also happy to report that the JS port is now used in PageSpeed and YSlow (as you probably know Firefox extensions are written in JavaScript)

Page Speed

YSlow

Building

If you want to play on your own with the source version of YUICompressor without waiting for the next release, you can build it like so:

  1. Checkout or download the code from http://github.com/yui/yuicompressor/
  2. Navigate to the root yuicompressor/ directory
  3. Type ant and hit enter

In order for this to work you need a somewhat recent Java SDK installed and also Ant running. (On the Mac, just do port install apache-ant to get Ant)

This is for the Java version, the JS version needs no building, of course.

Tests

There's a bunch of new tests now (and if you want to contribute to the project, you can always write more tests and test cases for any bugs), you can run them with the suite script that Isaac wrote:

  1. cd tests/
  2. ./suite.sh

One thing I added (and loved it) is to run the tests using the JS port as well. Since the JS min part is using Mozilla's Rhino (slightly modified), Rhino is part of the code. So I'm using this already available JavaScript interpreter to run the JS port. Convenient.

The procedure to write new tests is simple:

  1. Create source CSS file in the tests/ directory, e.g. new-test.css
  2. Create a new file with the expected result and name it with a .min extension, e.g. new-test.css.min

You can use the handy-dandy online version to help with the tests creation.

Next time

With those details out of the way, the next time I'll talk more about the different things that CSSMin does to your CSS code. Thanks for reading!

 

Uncompressed data in base64? Probably not

Thursday, February 4th, 2010

The beauty of experimentation is that failures are just as fun as successes. Warning: this post is about a failure, so you can skip it altogether :)

The perf advent calendar was my attempt to flush out a bunch of stuff, tools and experiments I was doing but never had the time to talk about. I guess 24 days were not enough. Here's another little experiment I made some time ago and forgot about. Let me share before it disappears to nothing with the next computer crash.

I've talked before about base64-encoded data URIs. I mentioned that according to my tests base64 encoding adds on average 33% to the file size, but gzipping brings it back, sometimes to less than the original.

Then I saw a comment somewhere (reddit? hackernews?) that the content before base64-encoding better be uncompressed, because it will be gzipped better after that. It made sense, so I had to test.

"Whoa, back it up... beep, beep, beep" (G. Constanza)

When using data URIs you essentially do this:

  1. take a PNG (which contains compressed data),
  2. base64 encode it
  3. shove it into a CSS
  4. serve the resulting CSS gzipped (compressed)

See how it goes: compress - encode - compress again. Compressing already compressed data doesn't sound like a good idea, so it sounds believable that skipping the first compression might give better results. Turns out it's not exactly the case.

Uncompressed PNG?

The PNG format contains information in "chunks". At the very least there's header (IHDR), data (IDAT) and end (IEND) chunks. There could be other chunks such as transparency, background and so on, but these three are required. The IDAT data chunk is compressed to save space, but it looks like it doesn't have to be.

PNGOut has an option to save uncompressed data, like
$ pngout -s4 -force file.png

This is what I tried - took several compressed PNGs, uncompressed them (with PNGOut's -s4), then encoded both with base64 encoding, put them in CSS, gzip the CSS and compared file sizes.

Code

<?php
// images to work with
$images = array(
  'html.png',
  'at.png',
  'app.png',
  'engaged.png',
  'button.png',
  'pivot.png'
);
//$images[] = 'sprt.png';
//$images[] = 'goog.png';
//$images[] = 'amzn.png';
//$images[] = 'wiki.png';

// css strings to write to files
$css1 = "";
$css2 = "";

foreach ($images as $i) {

  // create a "d" file, d as in decompressed
  copy($i, "d$i");
  $cmd = "pngout -s4 -force d$i";
  exec($cmd);

  // selector
  $sel = str_replace('.png', '', $i);

  // append new base64'd image 
  $file1 = base64_encode(file_get_contents($i));
  $css1 .= ".$sel {background-image: url('data:image/png;base64,$file1');}\n";
  $file2 = base64_encode(file_get_contents("d$i"));
  $css2 .= ".$sel {background-image: url('data:image/png;base64,$file2');}\n";

}

// write and gzip files
file_put_contents('css1.css', $css1);
file_put_contents('css2.css', $css2);
exec('gzip -9 css1.css');
exec('gzip -9 css2.css');

?>

Results

I tried to keep the test reasonable and used real life images - first the images that use base64 encoding in Yahoo! Search results. Then kept adding more files to grow the size of the result CSS - added Y!Search sprite, Google sprite, Amazon sprite and Wikipedia logo.

test with compressed PNG, bytes with uncompressed PNG, bytes difference, %
Y!Search images 700 1506 54%
previous + Y!Search sprite 5118 8110 36%
previous + Google sprite 27168 40836 33%
previous + Amazon sprite + Wikipedia logo 55804 79647 29%

Clearly starting with compressed images is better. Looks like the difference becomes smaller as the file sizes increase, it's possible that for very big files starting with uncompressed image could be better, but shoving more than 50K of images inline into a CSS file seems to be missing the idea of data URIs. I believe the idea is to use data URIs (instead of sprites) for small decoration images. If an image is over 50K it better be a separate request and cached, otherwise s small CSS tweak will invalidate the cached images.

 

One-click Minifier Gadget (OMG) – initial checkin

Sunday, January 31st, 2010

So I've been thinking and talking to folks about this idea of having one-stop shop for all your minification needs. Minification of JS and CSS as well as image optimization helps site performance by reducing download sizes. This is good. But not a lot of people do it.

People don't do it, because it's a PITA :) It's simple enough, but with deadlines upon you and all that, you don't want to do an extra step. That's why having a build process helps, by automating this. But setting up a build process is yet another PITA. So it goes.

So my idea was to help busy designers and developers, that wouldn't invest their time researching which minifiers are good, downloading setting up, learning about the 10+ PNG optimization tools... That's how the the idea for the one-click OMG tool came about. (One-drag is more appropriate, come to think of it...) One tool that runs on all operating systems - Win, Mac, Linux - and delivers all minification and optimization tools you need as one package.

Running

Running the tool is as simple as drag/dropping a bunch of files and directories. Here I've dropped "wordpress" directory. The tool recursively looks into the dropped files for things it can optimize. More information here.

OMG screenshot

Download

Version 0.0.1 is here. It doesn't do image optimization, only JS and CSS minification, but please feel free to download and give it a shot. Unzip the package for your OS and run omg.exe (Windows), OMG.app (Mac), or the omg binary (Linux)

Open source

The code is on GitHub. Fork and enjoy.

The developer's notes are there too - how to setup, run, package. Also a list of todos if you want to help.

Next?

This is just a preliminary version. Feel free to join, comment, suggest. Hate the name? Say so :)

Personally, looks like my plate is very full for the next moth or two, so I probably won't be actively working on the tool. I hope though the foundation is good enough and relatively documented, should be easy to pick up if anyone's interested in contributing.

Built with XUL

This has been a learning experience for me with XULRunner. I loved it. I love the idea of being able to create cross-OS desktop apps with JavaScript alone.

Behind the scenes, I'm using my JavaScript port of YUICompressor's CSSmin and Doug Crockford's JSMin. JSMin should be replaced with YUICompressor (or Google closure compiler) in the next release.

 

CSS performance: UI with fewer images

Wednesday, December 23rd, 2009

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

Dec 23 This post is the one-before-last article in the 2009 performance advent calendar experiment.

Often performance improvements come with their drawbacks, sometimes improving performance causes pains in other parts of the development process or strips stuff from the final product. Sometimes there's even a conflict where you have to pick: slow, unusable and beautiful or fast and looking like hacked with a blunt axe. But it doesn't have to be this way.

This post outlines some approaches to achieving common UI elements using CSS tricks in as many browsers as possible while using as fewer images as possible. Some of the tricks are brand new, some are very, very old, IE5.5. old. They all have in common the "fewer or no images" mantra. Using fewer images comes with some pronounced benefits:

  • less time spent in Photoshop
  • lighter page, less HTTP requests, less image payload
  • fewer elements in the sprite to maintain (and sometimes fewer sprites) which means longer lived sprites with fewer updates and cache invalidation
  • generally easier maintenance - it's much easier to change a color value than to update and push a new image version

Sometimes some browsers may not be fully supported but that's ok - as long as there's progressive enhancement and the basic page is usable, people rarely notice 1px glows and other ornaments.

So let's get started. BTW, a test page with the stuff discussed in the post is here.

Rounded corners

Yep, let's tackle the biggie.

Forget rounded corners in browser that don't support border-radius. Period. It may be hard to argue this case, but definitely try. Doing rounded corners any other way than border-radius is a pain - it adds markup bloat, it makes you create more images or sprite elements. It's tougher to update. Just forget it. Forget rounded corners in IE < 9 (as rumor has it border-radius is coming to IE9). People may argue that IE is important for your audience. No doubt that's true, but rounded corners are not so important for the audience. Show your designer Yahoo Search results page - the sidebar on the left-hand side. Not very rounded in IE. Do you think this was an easy battle - losing rounded corners in IE for such a high-profile site? Ask the man who won the battle ;)

So starting with a normal module - head, body and border:

The markup - nice and clean:

  <div class="module">
    <div class="hd"><h3>This is the header</h3></div>
    <div class="bd">
      <p>Here comes the content</p>
      <p>Here comes some more</p>
      <p>You can never have too much content, because
         content is king, right?
      </p>

    </div>
  </div>

Some fairly simple border radius to support Firefox, Webkit (Safari, Chrome, iPhone...) and, since a few days ago, Opera 10.5 alpha:

.module {
  -moz-border-radius: 9px;
  -webkit-border-radius: 9px;
  border-radius: 9px;
}

Result:

This is it! Easy-peasy, lemon squeezy.

Now, it's a little annoying to write three declarations for the same thing, but, hey - beats images and extra markup hands down. Also annoying are the differences when setting individual corners (-moz-border-radius-topleft is -webkit-border-top-left-radius). In this case we need to also round the header (class .hd) so it doesn't bleed through the beautifully rounded corners:

.hd {
  -moz-border-radius: 8px 8px 0 0;
  -webkit-border-top-left-radius: 8px;
  -webkit-border-top-right-radius: 8px;
  border-radius: 8px 8px 0 0;
}

Verdict:

  • Full support: Firefox, Safari, Chrome, Opera 10.5
  • Fallbacks: IE (corners are not rounded)

Drop shadows and glows

Another favorite effect designers love - dropping shadows. It's easy to enhance that existing .module without any new images:

.module {
  /* offset left, top, thickness, color with alpha */
  -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.5);
  -moz-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.5);
  box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.5);
  /* IE */
  filter:progid:DXImageTransform.Microsoft.dropshadow(OffX=5, OffY=5, Color='gray');
  /* slightly different syntax for IE8 */
  -ms-filter:"progid:DXImageTransform.Microsoft.dropshadow(OffX=5, OffY=5, Color='gray')";
}

And now our module casts a shadow:

Now two notes for IE: first the shadow doesn't have alpha so it's not as nice and second, this filter may not play along with other filters in the same module. But the shadow is cast and that's a check for IE too, even IE5.5!

You may notice that in this case we basically need to more or less repeat the same declaration three times and the IE declaration two times. This is irksome, but hopefully keeping the strings close together should help gzip compression.

As for glowing, it's the same thing in FF, Webkit, Opera, only without any offset. For IE, there's a different filter called glow:

.glow {
  -webkit-box-shadow: 0 0 10px rgba(50, 50, 50, 0.8);
  -moz-box-shadow: 0 0 10px rgba(50, 50, 50, 0.8);
  box-shadow: 0 0 10px rgba(50, 50, 50, 0.5);
  filter:progid:DXImageTransform.Microsoft.glow(Strength=5, Color='gray');
  -ms-filter:"progid:DXImageTransform.Microsoft.glow(Strength=3, Color='gray')";
}

I added these declaration to a new class .glow so I can add the class name to modules that need to glow. The result:

The result as it glows in IE:

Now you see why I added only 3 pixels glow in IE and whole 5 in the rest. The IE glow is a little .. interesting. Also in IE8 (could be my VM, in IE6 XP no VM all looks OK) the glow seems to move slightly when you hover over the module.

Verdict for shadows and glows:

  • Full support: FF, Safari, Chrome, Opera, IE5.5 and up

More info:

Gradients

Ah, gradients. Sometimes so subtle that we, muggles and other mere mortals, don't see them even when we try our hardest. But for the designer they could be life/death situation.

Let's make the head (class .hd) of our module a gradient without any images:

.hd {background-image: -moz-linear-gradient(top, #641d1c, #f00);
  background-image: -webkit-gradient(linear, left top, left bottom, from(#641d1c), to(#f00));
  filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#ff641d1c,endColorstr=#ffff0000);
  -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#ff641d1c,endColorstr=#ffff0000)";
}

The result:

What a beautiful (code-speaking, of course, not so sure about visually beautiful) module. It has rounded corners, drop shadows and a gradient and so far we haven't used even a single image. Which means this reddish module can become blue, green or, god forbid, pink - with a single tweak in the code, the CMS or the user preferences (if you're building a social network for example).

Gradients verdict:

  • Full support: FF, Safari, Chrome, IE
  • Fallbacks: Opera (solid color)

More info:

... and RGBA for all

Being able to set the transparency of the background without affecting the transparency of the foreground (the text) is quite handy. That's why there's rgba() in CSS (red, green, blue, alpha). IE is not yet supporting it, but we can use the gradient filter which does support transparency. In this case we don't need the actual gradient so we set start and end color to the same thing. Also the background: transparent is needed for the whole thing to work in IE:

.rgba {
  background-color: transparent;
  background-color: rgba(200,200,200,0.8);
  filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#99dddddd,endColorstr=#99dddddd);
  -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#99dddddd,endColorstr=#99dddddd)";
}

The result is pleasantly cross-browser:

RGBA verdict

  • Full support: Firefox, Safari, Opera, Chrome, IE

Rotating images

It happens that sometimes you use the same image only flipped. For example open/close thingies, menus and such. How about reusing the same image and rotating it with CSS?

.arrow {background: url(arrow.png) no-repeat; display: block; float: left; width: 33px; height: 33px;}
.right{ /* this is the original image*/ }
.left {
  -moz-transform: rotate(180deg);-webkit-transform: rotate(180deg); -o-transform: rotate(180deg);
  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2);
  -ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";}
.up {
  -moz-transform: rotate(270deg);-webkit-transform: rotate(270deg); -o-transform: rotate(270deg);
  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3);
  -ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";}
.down {
  -moz-transform: rotate(90deg);-webkit-transform: rotate(90deg); -o-transform: rotate(90deg);
  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1);
  -ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";
}

Here's the result. Single image:

Arrow

Result:

the HTML:

<span class="arrow right"></span>
<span class="arrow left"></span>
<span class="arrow up"></span>
<span class="arrow down"></span>

You may notice that the CSS could be quite verbose for saving such small images. It's highly recommended you add the rotation code to a class and use the class name when necessary instead of repeating the same long declaration for every use case or image. Then pray to the gods of compression that this thing gzips well ;)

Verdict

  • Full support: Firefox, IE, Safari, Opera, Chrome

Multiple UI elements with the same background image

The last few tricks have something in common - they each use one background image. The background images are very small - usually around 100 bytes. The tiny image has some transparency to it and is placed as a background-image which sits on top of a background-color. Because of the transparency, the background color shines through, but differently depending on the transparency level of the image above it.

The result is - different UI elements with different colors (and even different hover colors) which can be part of CMS or part of user's skinning and they all reuse the same tiny background. So what can we do this way? A lot of interesting background effects, but here's a few.

Glossy buttons

Here's the end result:

All these buttons share the same background image. The image is 1x1000 and repeated horizontally. The 1000 is just to be safe, very safe, because 50, 100 or 1000 doesn't affect the file size which just a mere 100 bytes. The upper half of the image is a little less transparent. The lower half is 100% transparent. When placed on top of the solid color the whole thing looks shiny and glossy. And you can change the color any way you like.

The HTML:

<p class="button">button1<p>
<p class="button button2">button2<p>
<p class="button button3">button3<p>
<p class="button button4">button4<p>
<p class="button button5">button5<p>
<p class="button button6">button6<p>

And the CSS can't be simpler:

.button {
  background-image:url(http://tools.w3clubs.com/mask/mask.php?x=1000&type=h);
  background-position: center;
}
.button:hover {background-color: #F29222;}
.button2 {background-color: #A41D1C;}
.button3 {background-color: #0F6406;}
.button4 {background-color: #333f79;}
.button5 {background-color: black;}
.button6 {background-color: orange; color: black;}

Actually in the test page I have inlined the image with data URI to save the whole HTTP request for such a teeny image.

As you can see in the URL of the background, I've done a little script to generate some background images:
http://tools.w3clubs.com/mask/mask.php?x=1000&type=h
The image generator's source code is right here.

Stripes

Same technique - but used to generate striped background:

It's basically the same code, only using a different call to the image generator to give us a different background image.

HTML:

  <div class="module stripe earth glow">
    <div class="bd">
      <p>striped background</p>
    </div>

  </div>

  <div class="module stripe tech glow">
    <div class="hd phony stripe"><h3>stripity-stripes</h3></div>
    <div class="bd">
      <p>striped background with the same background image</p>
    </div>
  </div>

CSS:

.stripe {background-image: url(http://tools.w3clubs.com/mask/mask.php?type=stripe);}
.earth  {background-color: olive;}
.tech   {background-color: #bbb;}
.phony  {background-color: #0F6406;}

Again, this image can be a data URI so we save the single HTTP request.

And another gradient

So if you don't like the previously discussed way to do gradients, here's another one. The same trick with the solid color background and a semi-transparent image on top.

Result:

The background images as generated by the service are:
// lighter at the top
http://tools.w3clubs.com/mask/mask.php?type=gradient
// darker at the top
http://tools.w3clubs.com/mask/mask.php?type=gradient&flip=1

Again, you can see the test page here and the source for the image generation is here.

For yet another example of this technique check my post on this (abandoned) blog phonydev.com. There I take an image and a mask image generated by the same script and overlay to achieve an iPhone-like glossy button.

iphone glossiness

Thanks!

Kind of long post, but I hope you're excited about removing a bunch of images from your future designs. If I've omitted some details, please let me know in the comments.

 

The new game show: “Will it reflow?”

Saturday, December 19th, 2009

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

Dec 19 This post is part of the 2009 performance advent calendar experiment. Stay tuned for the articles to come.

Intrigued by Luke Smith's comment and also Alois Reitbauer's comment on the previous post about rendering I did some more testing with dynaTrace and SpeedTracer. Also prompted by this tweet, I wanted to provide an example of avoiding reflows by using document fragments as well as hiding elements with display: none. (btw, sorry that I'm slow to respond to tweets and blog comments, just too much writing lately with the crazy schedule, but I do appreciate every tweet and comment!)

So welcome to the new game show: "Will it reflow?" where we'll look into a few cases where it's not so clear if the browser will do a reflow or just a repaint. The test page is here.

Changing classnames

The first test is fairly straightforward - we only want to check what happens when you change the class name of an element. So using "on" and "off" class names and changing them on mouse over.

.on {background: yellow; border: 1px solid red;}
.off {background: white; border: 1px dashed green;}

Those CSS rules shouldn't trigger a reflow, because no geometry is being changed. Although the test is pushing it a bit by changing borders, which may affect geometry, but not in this case.

The test code:

// test #1 - class name change - will it reflow?
var onoff = document.getElementById('on-off');
onoff.onmouseover = function(){
  onoff.className = 'on' ;
};
onoff.onmouseout = function(){
  onoff.className = 'off';
};

Sooo.. will it reflow?

In Chrome - no! In IE - yes.

In IE, even changing the class name declarations to only change color, which is sure not to cause reflow, still caused a reflow. Looks like in IE, any type of className change causes a reflow.

cssText updates

The recommended way to update multiple styles in one shot (less DOM access, less reflows) is to update the element's style.cssText property. But.. will it reflow when the style changes do not affect geometry?

So let's have an element with a style attribute:

...style="border: 1px solid red; background: white"...

The JavaScript to update the cssText:

// test #2 - cssText change - will it reflow?
var csstext = document.getElementById('css-text');
csstext.onmouseover = function(){
  csstext.style.cssText += '; background: yellow; border: 1px dashed green;';
};
csstext.onmouseout = function(){
  csstext.style.cssText += '; background: white; border: 1px solid red;';
};

Will it reflow?

In Chrome - no! In IE - yes.

Even having cssText (and the initial style) only play with color, there's still a reflow. Even trying to just write the cssText property (as opposed to read/write with += ) still causes a reflow. The fact that cssText property is being updated causes IE to reflow. So there might be cases where setting individual properties separately (like style.color, style.backgroundColor and so on) which don't affect geometry, might be preferable to touching the cssText.

Next contestant in the game show is...

addRule

Will the browser reflow when you update stylesheet collections programatically? Here's the test case using addRule and removeRule (which in Firefox are insertRule/deleteRule):

// test #3 - addRule - will it reflow?
var ss = document.styleSheets[0];
var ruletext = document.getElementById('ruletext');
ruletext.onmouseover = function(){
  ss.addRule('.bbody', 'color: red');
};
ruletext.onmouseout = function(){
  ss.removeRule(ss.rules.length - 1);
};

Will it? Will it?

In Chrome - yes. The fact that style rules in the already loaded stylesheet have been touched, causes Chrome to reflow and repaint. Even though class .bbody is never used. Same when creating a new rule with selector body {...} - reflow, repaint.

In IE there's a repaint definitely, and there's also a kind of reflow. Looks like dynaTrace shows two kinds of rendering calculation indicators: "Calculating generic layout" and "Calculating flow layout". Not sure what is the difference (web searches disappointingly find nada/zero/rien for the first string and my previous blog post for the second). Hopefully "generic" would be less expensive than "flow".

display: none

In my previous post I boldly claimed that elements with display: none will not have anything to do with the render tree. IE begs to differ (thanks to dynaTrace folks for pointing that out).

A good way to minimize reflows is to update the DOM tree "offline" out of the live document. One way to do so is to hide the element while updates are taking place and then show it again.

Here's a test case where rendering and geometry are affected by simply adding more text content to an element by creating new text nodes.

// test #4 - display: none - will it reflow
var computed, tmp;
var dnonehref = document.getElementById('display-none');
var dnone = document.getElementById('bye');
if (document.body.currentStyle) {
  computed = dnone.currentStyle;
} else {
  computed = document.defaultView.getComputedStyle(dnone, '');
}

dnonehref.onmouseover = function() {
  dnone.style.display = 'none';
  tmp = computed.backgroundColor;
  dnone.appendChild(document.createTextNode('No mo tests. '));
  tmp = computed.backgroundColor;
  dnone.appendChild(document.createTextNode('No mo tests. '));
  tmp = computed.backgroundColor;
  dnone.appendChild(document.createTextNode('No mo tests. '));
  tmp = computed.backgroundColor;
}
dnonehref.onmouseout = function() {
  dnone.style.display = 'inline';
}

Will it reflow?

In Chrome - no. Although it does do "restyle" (calculating non-geometric styles) every time a text node is added. Not sure why this restyling is needed.

In IE - yes. Unfortunatelly display: none seems to have no effect on rendering in IE, it still does reflows. I tried with removing the show/hide code and having the element hidden from the very beginning (with an inline style attribute). Same thing - reflow.

document fragment

Another way to preform updates off-DOM is to create a document fragment and once ready, shove the fragment into the DOM. The beauty is that the children of the fragment get copied, not the fragment itself, which makes this method pretty convenient.

Here's the test/example. And will it reflow?

// test #5 - fragment - will it reflow
var fraghref = document.getElementById('fragment');
var fragment = document.createDocumentFragment();
fraghref.onmouseover = function() {
  fragment.appendChild(document.createTextNode('No mo tests. '));
  tmp = computed.backgroundColor;
  fragment.appendChild(document.createTextNode('No mo tests. '));
  tmp = computed.backgroundColor;
  fragment.appendChild(document.createTextNode('No mo tests. '));
  tmp = computed.backgroundColor;
}
fraghref.onmouseout = function() {
  dnone.appendChild(fragment);
}

In Chrome - no! And no rendering activities take place until the fragment is added to the live DOM. Then, just like with display: none a restyle is being performed for every new text node inserted. And even though the behavior is the same for fragments as for updating hidden elements, fragments are still preferable, because you don't need to hide the element (which will cause another reflow) initially.

In IE - no reflow! Only when you add the final result to the live DOM.

Thanks!

Thank you for reading. Tomorrow if all goes well there should be a final post related to JavaScript and then moving on to ... other topics ;)

 

Rendering: repaint, reflow/relayout, restyle

Thursday, December 17th, 2009

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

Dec 17 This post is part of the 2009 performance advent calendar experiment. Stay tuned for the articles to come.

Nice 5 "R" words in the title, eh? Let's talk about rendering - a phase that comes in the Life of Page 2.0 after, and sometimes during, the waterfall of downloading components.

So how does the browser go about displaying your page on the screen, given a chunk of HTML, CSS and possibly JavaScript.

The rendering process

Different browsers work differently, but the following diagram gives a general idea of what happens, more or less consistently across browsers, once they've downloaded the code for your page.

Rendering process in the browser

  • The browser parses out the HTML source code (tag soup) and constructs a DOM tree - a data representation where every HTML tag has a corresponding node in the tree and the text chunks between tags get a text node representation too. The root node in the DOM tree is the documentElement (the <html> tag)
  • The browser parses the CSS code, makes sense of it given the bunch of hacks that could be there and the number of -moz, -webkit and other extensions it doesn't understand and will bravely ignore. The styling information cascades: the basic rules are in the User Agent stylesheets (the browser defaults), then there could be user stylesheets, author (as in author of the page) stylesheets - external, imported, inline, and finally styles that are coded into the style attributes of the HTML tags
  • Then comes the interesting part - constructing a render tree. The render tree is sort of like the DOM tree, but doesn't match it exactly. The render tree knows about styles, so if you're hiding a div with display: none, it won't be represented in the render tree. Same for the other invisible elements, like head and everything in it. On the other hand, there might be DOM elements that are represented with more than one node in the render tree - like text nodes for example where every line in a <p> needs a render node. A node in the render tree is called a frame, or a box (as in a CSS box, according to the box model). Each of these nodes has the CSS box properties - width, height, border, margin, etc
  • Once the render tree is constructed, the browser can paint (draw) the render tree nodes on the screen

The forest and the trees

Let's take an example.

HTML source:

<html>
<head>
  <title>Beautiful page</title>
</head>
<body>

  <p>
    Once upon a time there was
    a looong paragraph...
  </p>

  <div style="display: none">
    Secret message
  </div>

  <div><img src="..." /></div>
  ...

</body>
</html>

The DOM tree that represents this HTML document basically has one node for each tag and one text node for each piece of text between nodes (for simplicity let's ignore the fact that whitespace is text nodes too) :

documentElement (html)
    head
        title
    body
        p
            [text node]

        div
            [text node]

        div
            img

        ...

The render tree would be the visual part of the DOM tree. It is missing some stuff - the head and the hidden div, but it has additional nodes (aka frames, aka boxes) for the lines of text.

root (RenderView)
    body
        p
            line 1
	    line 2
	    line 3
	    ...

	div
	    img

	...

The root node of the render tree is the frame (the box) that contains all other elements. You can think of it as being the inner part of the browser window, as this is the restricted area where the page could spread. Technically WebKit calls the root node RenderView and it corresponds to the CSS initial containing block, which is basically the viewport rectangle from the top of the page (0, 0) to (window.innerWidth, window.innerHeight)

Figuring out what and how exactly to display on the screen involves a recursive walk down (a flow) through the render tree.

Repaints and reflows

There's always at least one initial page layout together with a paint (unless, of course you prefer your pages blank :) ). After that, changing the input information which was used to construct the render tree may result in one or both of these:

  1. parts of the render tree (or the whole tree) will need to be revalidated and the node dimensions recalculated. This is called a reflow, or layout, or layouting. (or "relayout" which I made up so I have more "R"s in the title, sorry, my bad). Note that there's at least one reflow - the initial layout of the page
  2. parts of the screen will need to be updated, either because of changes in geometric properties of a node or because of stylistic change, such as changing the background color. This screen update is called a repaint, or a redraw.

Repaints and reflows can be expensive, they can hurt the user experience, and make the UI appear sluggish.

What triggers a reflow or a repaint

Anything that changes input information used to construct the rendering tree can cause a repaint or a reflow, for example:

  • Adding, removing, updating DOM nodes
  • Hiding a DOM node with display: none (reflow and repaint) or visibility: hidden (repaint only, because no geometry changes)
  • Moving, animating a DOM node on the page
  • Adding a stylesheet, tweaking style properties
  • User action such as resizing the window, changing the font size, or (oh, OMG, no!) scrolling

Let's see a few examples:

var bstyle = document.body.style; // cache

bstyle.padding = "20px"; // reflow, repaint
bstyle.border = "10px solid red"; // another reflow and a repaint

bstyle.color = "blue"; // repaint only, no dimensions changed
bstyle.backgroundColor = "#fad"; // repaint

bstyle.fontSize = "2em"; // reflow, repaint

// new DOM element - reflow, repaint
document.body.appendChild(document.createTextNode('dude!'));

Some reflows may be more expensive than others. Think of the render tree - if you fiddle with a node way down the tree that is a direct descendant of the body, then you're probably not invalidating a lot of other nodes. But what about when you animate and expand a div at the top of the page which then pushes down the rest of the page - that sounds expensive.

Browsers are smart

Since the reflows and repaints associated with render tree changes are expensive, the browsers aim at reducing the negative effects. One strategy is to simply not do the work. Or not right now, at least. The browser will setup a queue of the changes your scripts require and perform them in batches. This way several changes that each require a reflow will be combined and only one reflow will be computed. Browsers can add to the queued changes and then flush the queue once a certain amount of time passes or a certain number of changes is reached.

But sometimes the script may prevent the browser from optimizing the reflows, and cause it to flush the queue and perform all batched changes. This happens when you request style information, such as

  1. offsetTop, offsetLeft, offsetWidth, offsetHeight
  2. scrollTop/Left/Width/Height
  3. clientTop/Left/Width/Height
  4. getComputedStyle(), or currentStyle in IE

All of these above are essentially requesting style information about a node, and any time you do it, the browser has to give you the most up-to-date value. In order to do so, it needs to apply all scheduled changes, flush the queue, bite the bullet and do the reflow.

For example, it's a bad idea to set and get styles in a quick succession (in a loop), like:

// no-no!
el.style.left = el.offsetLeft + 10 + "px";

Minimizing repaints and reflows

The strategy to reduce the negative effects of reflows/repaints on the user experience is to simply have fewer reflows and repaints and fewer requests for style information, so the browser can optimize reflows. How to go about that?

  • Don't change individual styles, one by one. Best for sanity and maintainability is to change the class names not the styles. But that assumes static styles. If the styles are dynamic, edit the cssText property as opposed to touching the element and its style property for every little change.
    // bad
    var left = 10,
        top = 10;
    el.style.left = left + "px";
    el.style.top  = top  + "px";
    
    // better 
    el.className += " theclassname";
    
    // or when top and left are calculated dynamically...
    
    // better
    el.style.cssText += "; left: " + left + "px; top: " + top + "px;";
  • Batch DOM changes and perform them "offline". Offline means not in the live DOM tree. You can:
    • use a documentFragment to hold temp changes,
    • clone the node you're about to update, work on the copy, then swap the original with the updated clone
    • hide the element with display: none (1 reflow, repaint), add 100 changes, restore the display (another reflow, repaint). This way you trade 2 reflows for potentially a hundred
  • Don't ask for computed styles excessively. If you need to work with a computed value, take it once, cache to a local var and work with the local copy. Revisiting the no-no example from above:
    // no-no!
    for(big; loop; here) {
        el.style.left = el.offsetLeft + 10 + "px";
        el.style.top  = el.offsetTop  + 10 + "px";
    }
    
    // better
    var left = el.offsetLeft,
        top  = el.offsetTop
        esty = el.style;
    for(big; loop; here) {
        left += 10;
        top  += 10;
        esty.left = left + "px";
        esty.top  = top  + "px";
    }
  • In general, think about the render tree and how much of it will need revalidation after your change. For example using absolute positioning makes that element a child of the body in the render tree, so it won't affect too many other nodes when you animate it for example. Some of the other nodes may be in the area that needs repainting when you place your element on top of them, but they will not require reflow.

Tools

Only about a year ago, there was nothing that can provide any visibility into what's going on in the browser in terms of painting and rendering (not that I am aware of, it's of course absolutely possible that MS had a wicked dev tool no one knew about, buried somewhere in MSDN :P ). Now things are different and this is very, very cool.

First, MozAfterPaint event landed in Firefox nightlies, so things like this extension by Kyle Scholz showed up. mozAfterPaint is cool, but only tells you about repaints.

DynaTrace Ajax and most recently Google's SpeedTracer (notice two "trace"s :) ) are just excellent tools for digging into reflows and repaints - the first is for IE, the second for WebKit.

Some time last year Douglas Crockford mentioned that we're probably doing some really stupid things in CSS we don't know about. And I can definitely relate to that. I was involved in a project for a bit where increasing the browser font size (in IE6) was causing the CPU go up to 100% and stay like this for 10-15 minutes before finally repainting the page.

Well, the tools are now here, we don't have excuses any more for doing silly things in CSS.

Except, maybe, speaking of tools..., wouldn't it be cool if the Firebug-like tools showed the render tree in addition to the DOM tree?

A final example

Let's just take a quick look at the tools and demonstrate the difference between restyle (render tree change that doesn't affect the geometry) and reflow (which affects the layout), together with a repaint.

Let's compare two ways of doing the same thing. First, we change some styles (not touching layout) and after every change, we check for a style property, totally unrelated to the one just changed.

bodystyle.color = 'red';
tmp = computed.backgroundColor;
bodystyle.color = 'white';
tmp = computed.backgroundImage;
bodystyle.color = 'green';
tmp = computed.backgroundAttachment;

Then the same thing, but we're touching style properties for information only after all the changes:

bodystyle.color = 'yellow';
bodystyle.color = 'pink';
bodystyle.color = 'blue';

tmp = computed.backgroundColor;
tmp = computed.backgroundImage;
tmp = computed.backgroundAttachment;

In both cases, these are the definitions of the variables used:

var bodystyle = document.body.style;
var computed;
if (document.body.currentStyle) {
  computed = document.body.currentStyle;
} else {
  computed = document.defaultView.getComputedStyle(document.body, '');
}

Now, the two example style changes will be executed on click on the document. The test page is actually here - restyle.html (click "dude"). Let's call this restyle test.

The second test is just like the first, but this time we'll also change layout information:

// touch styles every time
bodystyle.color = 'red';
bodystyle.padding = '1px';
tmp = computed.backgroundColor;
bodystyle.color = 'white';
bodystyle.padding = '2px';
tmp = computed.backgroundImage;
bodystyle.color = 'green';
bodystyle.padding = '3px';
tmp = computed.backgroundAttachment;

// touch at the end
bodystyle.color = 'yellow';
bodystyle.padding = '4px';
bodystyle.color = 'pink';
bodystyle.padding = '5px';
bodystyle.color = 'blue';
bodystyle.padding = '6px';
tmp = computed.backgroundColor;
tmp = computed.backgroundImage;
tmp = computed.backgroundAttachment;

This test changes the layout so let's called it "relayout test", the source is here.

Here's what type of visualization you get in DynaTrace for the restyle test.

DynaTrace

Basically the page loaded, then I clicked once to execute the first scenario (requests for style info every time, at about 2sec), then clicked again to execute the second scenario (requests for styles delayed till the end, at about 4sec)

The tool shows how the page loaded and the IE logo shows onload. Then the mouse cursor is over the rendering activity following the click. Zooming into the interesting area (how cool is that!) there's a more detailed view:

dynatrace

You can clearly see the blue bar of JavaScript activity and the following green bar of rendering activity. Now, this is a simple example, but still notice the length of the bars - how much more time is spent rendering than executing JavaScript. Often in Ajax/Rich apps, JavaScript is not the bottleneck, it's the DOM access and manipulation and the rendering part.

OK, now running the "relayout test", the one that changes the geometry of the body. This time check out this "PurePaths" view. It's a timeline plus more information about each item in the timeline. I've highlighted the first click, which is a JavaScript activity producing a scheduled layout task.

dynatrace

Again, zooming into the interesting part, you can see how now in addition to the "drawing" bar, there's a new one before that - the "calculating flow layout", because in this test we had a reflow in addition to the repaint.

dynatrace

Now let's test the same page in Chrome and look at the SpeedTracer results.

This is the first "restyle" test zoomed into the interesting part (heck, I think I can definitely get cused to all that zooming :) ) and this is an overview of what happened.

speedtracer

Overall there's a click and there's a paint. But in the first click, there's also 50% time spent recalculating styles. Why is that? Well, this is because we asked for style information with every change.

Expanding the events and showing hidden lines (the gray lines were hidden by Speedtracer because they are not slow) we can see exactly what happened - after the first click, styles were calculated three times. After the second - only once.

speedtracer

Now let's run the "relayout test". The overall list of events looks the same:

speedtracer

But the detailed view shows how the first click caused three reflows (because it asked for computed style info) and the second click caused only one reflow. This is just excellent visibility into what's going on.

speedtracer

A few minor differences in the tools - SpeedTracer didn't show when the layout task was scheduled and added to the queue, DynaTrace did. And then DynaTrace didn't show the details of the difference between "restyle" and "reflow/layout", like SpeedTracer did. Maybe simply IE doesn't make a difference between the two? DynaTrace also didn't show three reflows instead of one in the different change-end-touch vs. change-then-touch tests, maybe that's how IE works?

Running these simple examples hundreds of times also confirms that for IE it doesn't matter if you request style information as you change it.

Here's some more data points after running the tests with enough repetitions:

  • In Chrome not touching computed styles while modifying styles is 2.5 times faster when you change styles (restyle test) and 4.42 times faster when you change styles and layout (relayout test)
  • In Firefox - 1.87 times faster to refrain from asking computed styles in restyle test and 1.64 times faster in the relayout test
  • In IE6 and IE8, it doesn't matter

Across all browsers though changing styles only takes half the time it takes to change styles and layout. (Now that I wrote it, I should've compared changing styles only vs. changing layout only). Except in IE6 where changing layout is 4 times more expensive then changing only styles.

Parting words

Thanks very much for working through this long post. Have fun with the tracers and watch out for those reflows! In summary, let me go over the different terminology once again.

  • render tree - the visual part of the DOM tree
  • nodes in the render tree are called frames or boxes
  • recalculating parts of the render tree is called reflow (in Mozilla), and called layout in every other browser, it seems
  • Updating the screen with the results of the recalculated render tree is called repaint, or redraw (in IE/DynaTrace)
  • SpeedTracer introduces the notion of "style recalculation" (styles without geometry changes) vs. "layout"

And some more reading if you find this topic fascinating. Note that these reads, especially the first three, are more in depth, closer to the browser, as opposed to closer to the developer which I tried to do here.

 

Give PNG a chance

Sunday, December 13th, 2009

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

Dec 13 This post is part of the 2009 performance advent calendar experiment. Stay tuned for the articles to come.

People are often afraid to use PNG because they think that:
a/ it doesn't work in all browsers, or
b/ filesizes are bigger than GIF

While these have some grain of truth to them, they are mostly misconceptions. Before addressing them, one quick background point - what's PNG8 and why it's cool.

PNG8

There are several types of PNG files, which can be grouped into those main kinds:

  • Truecolor PNGs with or without alpha transparency channel, also known as PNG24 and/or PNG32 (the one with alpha)
  • Grayscale PNGs with or without alpha
  • Indexed PNG, aka palette PNG, aka PNG8

PNG8 is like a GIF - it has a palette of 256 colors and supports transparency. While GIF supports true/false transparency (a pixel is either transparent or it isn't), PNG8 supports variable alpha transparency. Right there you see - PNG8 can do anything that GIF can, plus more.

There's a little glitch in IE6 where a semi-transparent pixels in PNG8 are seen as fully transparent, just like a GIF. So here's an option for progressive enhancement - you use the same image and IE6 gets a degraded GIF-like experience, while modern browsers get the full experience.

Here's an example, taken from this excellent article - modern browsers get the light bulb with the glow:

IE6 and under get the gracefully degraded experience and no glow:

Another pain point is that Photoshop doesn't produce semi-transparent PNG8 (although they came up with the name "png8" instead of saying palette or indexed PNG). Only Fireworks does export alpha transparent PNG8, which makes it a bit of a challenge. You also need a good designer to undertake this tricky task of making sure the image looks OK in both experiences. One way is to assume you're working with a GIF and then upgrade the experience with the carefully selected semi-transparent pixels. It could also help you keep the gif-like version in a layer and use other layers for the semi-transparent stuff, so you can quickly preview what the image will look like in IE6.

In any event - the important thing to remember is that in the worst case (IE6) PNG8 is as good as a GIF.

PNG doesn't work in browsers?

PNG works in browsers since forever with the exception of two edge cases:

  • the glitch where PNG8's semi-transparency is gone in IE6 (see above), but here GIF can't help you either
  • transparency in truecolor PNGs is shown as a solid (usually grey) color in IE6. But again - GIF can't help here either, because it doesn't support alpha (variable) transparency to begin with. People often use GIF to "solve" this problem (moving to GIF will mean potentially losing colors), but if you can solve with a GIF, you can solve it even better (and with smaller filesize) with PNG8

Another solution to the second problem is to use IE's AlphaImageLoader CSS filter (and there's a number of scripts to do so automatically), but this filter has serious performance drawbacks and should only be used as a last resort. Three things to try before resorting to AlphaImageLoader:

  1. Try PNG8 for progressively enhanced experience
  2. Try without transparency - if the background is a solid color, convert the image to use the solid color. In imagemagick you can use -flatten for this purpose:
    $ convert source.png -flatten -background yellow result.png
  3. Forget about IE6 :)

If you end up using AlphaImageLoader, make sure you use the underscore hack so that only IE6 users experience the performance degradation.

#some-element {
    background: url(image.png);
    _background: none;
    _filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='image.png', sizingMethod='crop');
}

PNGs are bigger than GIFs?

This misconception comes from the fact that people compare truecolor PNG with GIF which is not a fair comparison because you often compare image with thousands of colors (the PNG24) with an image with 256 colors (GIF). Often people work on an image in Photoshop or another program and when they decide to export for the web, they try PNG24, see that it's bigger and switch to GIF. But in this step GIF may strip a lot of colors. And if you're going to strip colors, well, PNG8 will give you the same colors and smaller filesize. (Another thing is that sometimes Photoshop does a poor job exporting the PNG8. If the PNG8 looks crappy, but the GIF is OK, then export as a GIF but then convert to PNG with another utility, such as optipng)

Again - PNG8 is the file format comparable to GIF and it's almost always smaller in filesize than GIF.

Comparing GIF vs. PNG filesizes

(This and the next experiment is something I did over an year ago, bored to death in the middle of the ocean on the board of a Carnival cruise ship, but since then I never really looked at the data. So here's my chance to flush some old data and clean up 20 Gigs of lets-keep-just-in-case test images :) )

Using Yahoo! image search web service I downloaded some GIFs (matching the queries "logo", "mail" and "graph"), ended up a little over 1700 images. Then I used optipng to convert them all to PNG and see the results.

I used OptiPNG simply with no special options:

$ optipng *.gif

As the next experiment will show, optipng can do better, so can pngout for example. So consider these results the least you can do to make GIFs smaller (by turning them to PNG)

So some stats from the experiment:

  • The average, median actually, GIF image on the web (last year, judging from this small sample) is 525x388 and has 139 colors (I just love semi-useless stats ;) )
  • The median GIF is 24K
  • After conversion to PNG, the median becomes 18K
  • The median savings from converting all GIFs to PNG is about 23%

Interestingly enough 4% of the images were smaller as GIFs - utter disappointment (and don't tell anyone!). So I had to try just a little harder. I didn't run OptiPNG with its best -o7 option, but ran PNGOut instead. The results is that now only 4 of the 1706 images were smaller as GIF. I'm pretty sure that trying a little harder (with PNGSlim, see yesterday's post) would've probably fixed it, but 4 out of 1700 is something I could live with. BTW, the images where OptiPNG failed to produce smaller PNG, then PNGOut converted with the ratio of 21% median savings. Not bad for taming the few shrew GIFs.

BTW, some GIFs lost over 100K of filesize, the max was over almost 600K savings! So, you never know.

If you like to look at numbers, here's a csv dump - the optipng results and the selected few that ran through PNGout.

So, take-home message: turn your GIFs into PNGs and win at minimum 20% fewer bytes over the network.

Comparing PNG optimizers

For this experiment I downloaded over 12000 images (again, Yahoo! search API) and ran them through a bunch of optimizers, sometimes with different options. In retrospect, it's probably not that useful of an experiment, because (see previous post) different optimizers specialize in different areas - compression, pre-compression filtering, chunks removal, etc, and your best bet is to run several tools. But still it's at least some data points (the cvs dump is here)

The images were 1000 matches for each of the searches for "baby", "background", "bkg", "flower", "graph", "graphic", "icon", "illustration", "kittens" (of course), "logo", "monkeys", "png", "transparency". After removing 4xxs, 5xxs and other mishaps and cleaning up a bit, I ended with over 10000 images. I ran them thorough:

  • pngcrush - pngcrush -rem alla -reduce before.png after.png
  • pngcrush-none - to keep all chunks pngcrush -rem none -reduce before.png after.png
  • pngcrush-brute - more filter attempts - pngcrush -rem alla -brute -reduce before.png after.png
  • pngout - pngout /q /y /force before.png after.png. default compression level in PNGOut is "extreme", so I tried two less extreme below
  • pngout-match - pngout /s2 /q /y /force before.png after.png
  • pngout-intense - pngout /s1 /q /y /force before.png after.png
  • pngrewrite - pngrewrite before.png after.png PNGRewrite only works with PNG8, it also converts truecolor to PNG8 whenever the truecolor happens to be under 256 colors,
  • optipng - optipng before.png -force -out after.png. OptiPNG's default level is 2 (of 7) so I had to try below and above the default:
  • optipng1 - optipng before.png -o1 -force -out after.png
  • optipng3 - optipng before.png -o3 -force -out after.png
  • optipng7 - optipng before.png -o7 -force -out after.png
  • advpng - cp before.png after.png; advpng -z -f -q after.png
  • advpng-insane - with the "insane" 4th level of compression cp before.png after.png; advpng -z4 -f -q after.png
  • deflopt - cp before.png after.png; deflopt -s -f after.png
  • pngoptimizercl -cp before.png after.png; pngoptimizercl -file:"after.png"

And the results:

Tool Median time to run Median savings Success rate
pngcrush 0.25s 6.06% 93.85%
pngcrush-none 0.23s 5.58% 90.22%
pngcrush-brute 3.08s 8.10% 96.31%
pngout 1.89s 12.21% 94.35%
pngout-match 0.22s 13.89% 44.57%
pngout-intense 1.63s 12.10% 94.22%
pngrewrite 0.07s 29.84% 22.37%
optipng 0.23s 7.32% 93.21%
optipng1 0.10s 4.24% 85.16%
optipng3 0.66s 7.10% 94.26%
optipng7 4.13s 7.57% 94.81%
advpng 0.34s 11.55% 52.47%
advpng-insane 0.76s 15.64% 56.09%
deflopt 0.34s 0.44% 96.94%
pngoptimizercl 0.48s 9.71% 97.99%

"Success rate" is how often the tool managed to produce a smaller result than the original. For example PNGRewrite's success rate is pretty low, because it only works with up to 256 colors. Median time to run is the median value that the tool takes to optimize one image.

And now, madames et monsieurs, introducing...

Give PNG a chance (.com)

I hope you'll find this as funny as I do, I thought it was pretty funny, at least in my head :)

My secret goal was that everybody who hears the song or watches the video, will think twice the next time when doing "Save for the Web..." in Photoshop.

Enjoy!

Music: Drums from GarageBand, I play two guitars, also bass (a guitar with effect actually) and vocals. If you think you hear a woman's voice, it's still me, with "Helium" effect. The MP3 is here. If you want to experiment with the song yourself, here's a zip with each channel as an MP3.

Video: It may be lousy, but it's all web dev :) It's all JavaScript and CSS. The video is a screen capture of the Safari window. Also there are no images, only HTML entities. Heavy use of -webkit-* animations and transitions. The source and a live version you can play in Safari is here. The StarWars-like effect is borrowed from here.

The http://givepngachance.com URL is currently pretty blank, but I intend to add more PNG-related stuff there. Oh, and the lyrics.

Thanks!

Thanks for reading. And watching. And listening. Peace. And give PNG a chance :)

 

Reducing the payload: compression, minification, 204s

Friday, December 11th, 2009

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

Dec 11 This post is part of the 2009 performance advent calendar experiment. Stay tuned for the next articles.

After removing all the extra HTTP requests you possibly can from your waterfall, it's time to make sure that those that are left are as small as they can be. Not only this makes your pages load faster, but it also helps you save on the bandwidth bill. Your weapons for fighting overweight component include: compression and minification of text-based files such as scripts and styles, recompression of some downloadable files, and zero-body components. (A follow-up post will talk about optimizing images.)

Gzipping plain text components

Hands down the easiest and at the same time quite effective optimization - turning on gzipping for all plain text components. It's almost a crime if you don't do it. Doesn't "cost" any development time, just a simple flip of a switch in Apache configuration. And the results could be surprisingly pleasant.

When Bill Scott joined Netflix, he noticed that gzip is not on. So they turned it on. And here's the result - the day they enabled it, the outbound traffic pretty much dropped in half (slides)

Netflix traffic after turning on gzipping

Gzip FAQ

How much improvement can you expect from gzip?
On average - 70% reduction of the file size!
Any drawbacks?
Well, there's a certain cost associated with the server compressing the response and the browser uncompressing it, but it's negligible compared to the benefits you get
Any browser quirks?
Sure, IE6, of course. But only in IE6 service pack 1 and fixed for after that. You can boldly ignore this edge case, but if you're extra paranoid you can disable gzip for this user agent
How to tell if it's on?
Run YSlow/PageSpeed and they'll will warn you if it's not on. If you don't have any of those tools just look at the HTTP headers with any other tool, e.g. Firebug, webpagetest.org. You should see the header:

Content-Encoding: gzip

provided, of course, that your browser claimed it supports compression by sending the header:

Accept-Encoding: gzip, deflate
What types of components should you gzip?
All text components:

  • javascripts
  • css
  • plain text
  • html, xml, including any other XML-based format such as SVG, also IE's .htc
  • JSON responses from web service calls
  • anything that's not a binary file...

You should also gzip @font-files like EOT, TTF, OTF, with the exception of WOFF. Average about 40% to be won there with font files.

How-to turn on gzipping

Ideally you need control over the Apache configuration. If not full control, at least most hosting providers will offer you ability to tweak configuration via .htaccess. If your host doesn't, well, change the host.

So just add this to .htaccess:

AddOutputFilterByType DEFLATE text/html text/css text/plain text/xml application/javascript application/json

If you're on Apache before version 2 or your unfriendly host don't allow any access to configuration, not all is lost. You can make PHP do the gzipping for you. It's not ideal but the gzip benefits are so pronounced that it's worth the try. This article describes a number of different options for gzipping when dealing with uncooperative hosts.

Rezipping

As Billy Hoffman discovered, there's potential for file size reduction with common downloadable files, which are actually zip files in disguise. Such files include:

  • Newer MS Office documents - DOCX, XLSX, PPTX
  • Open Office documents - ODT, ODP, ODS
  • JARs (Java Applets, anyone?)
  • XPI Firefox extensions
  • XAP - Silverlight applications

These ZIP files in disguise are usually not compressed with the maximum compression. If you allow such downloads from your website, consider recompressing them beforehand with maximum compression.

There could be anywhere from 1 to 30% size reduction to be won, definitely worth the try, especially since you can do it all on the command line, as part of the build process, etc. (re)Compress once, save bandwidth and offer faster downloads every time ;)

15% uncompressed traffic

Tony Gentilcore of Google reported his findings that a significant chunk of their traffic is still sent uncompressed. Digging into it he realized there's a number of anti-virus software and firewalls that will mingle with the browser's Accept-Encoding header changing into the likes of:

Accept-Encoding: xxxx, deflxxx
Accept-Enxoding: gzip, deflate

Since this is an invalid header, the server will decide that the browser doesn't support gzip and send uncompressed response. And why would the retarded anti-virus program do it? Because it doesn't want to deal with decompression in order to examine the content. Probably not to slow down the experience? In doing so it actually hurts the user to a greater extend.

So compression is important, but unfortunately it's not always present. That's why minification helps - not only because compressing minified responses is even smaller, but because sometimes there is no compression despite your best efforts.

Minification

Minification means striping extra code from your programs that is not essential for execution. The code in question is comments, whitespace, etc from styles and scripts, but also renaming variables with shorter names, and various other optimizations.

This is best done with a tool, of course, and luckily there a number of tools to help.

Minifying JavaScript

Some of the tools to minify JavaScript include:

How much size reduction can you expect from minification? To answer that I ran jQuery 1.3.2. through all the tools mentioned above (using hosted versions) and compared the sizes before/after and with/without gzipping the result of minification.

The table below lists the results. All the % figures are % of the original, so smaller is better. 29% means the file was reduced to 29% of its original version, or a saving of 71%

File original size size, gzipped % of original gzip, % of original
original 120619 35088 100.00% 29.09%
closure-advanced 49638 17583 41.15% 14.58%
closure 55320 18657 45.86% 15.47%
jsmin 73690 21198 61.09% 17.57%
packer 39246 18659 32.54% 15.47%
shrinksafe 69516 22105 57.63% 18.33%
yui 57256 19677 47.47% 16.31%

As you can see gzipping alone gives you about 70% savings, minification alone cuts script sizes with more than half and both combined (minifying then gzipping) can make your scripts 85% leaner. Verdict: do it. The concrete tool you use probably doesn't really matter all that much, pick anything you're comfortable with to run before deployment (or best, automatically during a build process)

Minifying CSS

In addition to the usual stripping of comments and whitespaces, more advanced CSS minification could include for example:

// before
#mhm {padding: 0px 0px 0px 0px;}
// after
#mhm{padding:0}

// before
#ha{background: #ff00ff;}
// after
#ha{background:#f0f}
//...

A CSS minifier is much less powerful than a JS minifier, it cannot rename properties or reorganize them, because the order matters and for example text-decoration:underline cannot get any shorter than that.

There's not a lot of CSS minifiers, but here's a few I tested:

  • YUI compressor - yes, the same YUI compressor that does JavaScript minification. I've actually ported the CSS minification part of it to JavaScript (it's in Java otherwise) some time ago. There's even an online form you can paste into to test. The CSS minifier is regular expression based
  • Minify is a PHP based JS/CSS minification utility started by Ryan Grove. The CSS minifier part is also with regular expressions, I have the feeling it's also based on YUICompressor, at least initially
  • CSSTidy - a parser and an optimizer written in PHP, but also with C version for desktop executable. There's also a hosted version. It's probably the most advanced optimizer in the list, being a parser it has a deeper understanding of the structure of the styleshets
  • HTML_CSS from PEAR - not exactly an optimizer but more of a general purpose library for creating and updating stylesheets server-side in PHP. It can be used as a minifier, by simply reading, then printing the parsed structure, which strips spaces and comments as a side effect.

Trying to get an average figure of the potential benefits, I ran these tools on all stylesheets from csszengarden.com, collected simply like:

<?php
$urlt = "http://csszengarden.com/%s/%s.css";
for ($i = 1; $i < 214; $i++) {
  $id = str_pad($i, 3, "0", STR_PAD_LEFT);
  $url = sprintf($urlt, $id, $id);
  file_put_contents("$id.css", file_get_contents($url));
}
?>

3 files gave a 404, so I ran the tools above on the rest 210 files. CSSTidy ran twice - once with its safest settings (which even keep comments in) and then with the most aggressive. The "safe" way to use CSSTidy is like so:

<?php
// dependencies, instance
include 'class.csstidy.php';
$css = new csstidy();

// options
$css->set_cfg('preserve_css',true);
$css->load_template('high_compression');

// parse
$css->parse($source_css_code);

// result
$min = $css->print->plain();
?>

The aggressive minification is the same only without setting the preserve_css option.

Running Minify is simple:

<?php
// dependencies, instance
require 'CSS.php';
$minifier = new Minify_CSS();

// minify in one shot
$min = $minifier->minify($source_css_string_or_url);

As for PEAR::HTML_CSS, since it's not a minifier, you only need to parse the input and print the output.

<?php
require 'HTML/CSS.php';

$options = array(
    'xhtml' => false,
    'tab' => 0,
    'oneline' => true,
    'groupsfirst' => false,
    'allowduplicates' => true,
);

$css = new HTML_CSS($options);
$css->parseFile($input_filename);
$css->toFile($output_filename);
// ... or alternatively if you want the result as a string
// $minified = $css->toString();

So I ran those tools on the CSSZenGarden 200+ files and the full table of results is here, below are just the averages:

  Original YUI Minify CSSTidy-safe CSSTidy-small PEAR
raw 100% 68.18% 68.66% 84.44% 63.29% 74.60%
gzipped 30.36% 19.89% 20.74% 28.36% 19.44% 20.20%

Again, the numbers are percentage of the original, so smaller is better. As you can see, on average gzip alone gives you 70% size reduction. The minification is not so successful as with JavaScript. Here even the best tool cannot reach 40% reduction (for JS it was usually over 50%). But nevertheless, gzip+minification on average gives you a reduction of 80% or more. Verdict: do it!

An important note here is that in CSS we deal with a lot of hacks. Since the browsers have parsing issues (which is what hacks often exploit), what about a poor minifier? How safe are the minifiers? Well, that's a subject for a separate study, but I know I can at least trust the YUICompressor, after all it's used by hundreds of Yahoo! developers daily and probably thousands non-Yahoos around the world. PEAR's HTML_CSS library also looks pretty safe because it has a simple parser that seems to tolerate all kinds of hacks. CSSTidy also claims to tolerate a lot of hacks, but given that the last version is two years old (maybe new hacks have surfaced meanwhile) and the fact that it's the most intelligent optimizer (knows about values, colors and so on) it should be approached with care.

204

Let's wrap up this lengthy posting with an honorable mention of the 204 No Content response (blogged before). It's the world's smallest componet, the one that has no body and a Content-Length of 0.

Often people use 1x1 GIFs for logging and tracking purposes and other types of requests that don't need a response. If you do this, you can return a 204 status code and no response body, only headers. Look no further that Google search results with your HTTP sniffer ON to see examples of 204 responses.

The way to send a 204 response from PHP is simply:

header("HTTP/1.0 204 No Content");

A 204 response saves just a little bit but, hey, every little bit helps.

And remember the mantra: every extra bit is a disservice to the user :)

Thank you for reading!

Stay tuned for the next article continuing the topic of reducing the component sizes as much as possible.

 

Duplicates and near-duplicates

Wednesday, December 9th, 2009

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

Dec 9 This post is part of the 2009 performance advent calendar experiment. Stay tuned for the next articles.

One of Yahoo!'s first batch of performance best practices has always been "Avoid duplicate scripts" (check Steve Souders' post). Later we added "... and styles". This is a pretty obvious, kind of a "Duh!" type of recommendation, it's like saying "Avoid sleep() in your server-side scripts". But it didn't come up out of thin air, duplicates were noticed on some quite high-profile sites.

Duplicates are easy to spot (and YSlow will warn you), but let's talk a bit about another concept - let's call it near-duplicates - when two components are similar, almost the same, but not quite.

Duplicate scripts and styles

As a refresher and a quick illustration of the effects of duplicate scripts and styles, fire off your HTTP sniffer and hit this test page.

(btw, this is a simple page I put up to test different YSlow scenarios, you can actually use it as a web service of sorts to create any type of components with different options)

Firefox 2 downloading both duplicate styles and scripts:

FF2 duplicate scripts and styles

IE6 and duplicate scripts:

IE duplicate scripts

Exact details of when/which browsers chose to download duplicates are not that interesting, it's obviously bad to waste time downloading the same resource. Even if no repeating download happens, the browser still has to parse through and execute the script/style for a second time.

Even if you have iframes you don't need to repeat the same JS/CSS in each frame, you can "borrow" them from the parent page, here's an example.

Near-duplicates

Near duplicates can be:

  • components with the exact same response bodies but different URLs causing the browser to do double work
  • components (images) that are too close to each other - in terms of looks or purpose. Only one component should be selected in this case.

Same component, different URLs

This could happen especially when you have user-generated content such as image uploads for profile photos and avatars in social sites, forums, images people put in comments on MySpace and so on.

Also images of stuff for sale (Craigslist, eBay). Often different sellers offering the same item would take the same photo from the manufacturer's site and upload it over and over again.

Luckily, PageSpeed warns about components with identical content, so those can be identified:

In the screenshot above, you see one image (2.3K) repeated 3 times, another (the iPhone, 1.7K) is repeated 4 times, and yet another one (2.8K) repeated 2 times.

It's not exactly trivial to avoid this type of duplications with user-generated content (for example, the first poster may delete the photo, in which case the second poster's photo will need to "shine through"). But it's not impossible, using for example a hash of the component's content as an identifier.

Loading...

Ajax loading indicators are a great idea to give feedback to the user that something is happening. They come in all shapes and sizes... sometimes on the same page, unfortunately. And again, sometimes it's the same stock image but used at different stages of gradual "ajaxification" of the page and with different URLs.

As we're moving more and more towards modular pages and client-side logic, often different modules on the same page are coded by different teams at different times, independently, without being aware of each other's assets. This way of building pages has it's challenges and one is that common components, such as Ajax loading indicators, should be shared.

Too similar modules

Along the same lines - different modules are sometimes created by different designers at different times. The result - one rounded corner box with 1px shadow and one with 2px shadow, both on the same page. Or two different shades of the same gray color, which no one can tell apart. That's just a waste. (See Nicole Sullivan's presentation for illustration, e.g. slides 44, 45)

Below is an example, can you tell that these 5 rounded corner boxes are all different - slightly different shadow, color or radius? How many different boxes does this page need?

Different sizes of the same image

It's highly recommended to not scale images in HTML (or CSS). If you need a 100x100 image you don't use a 400x400 one with <img width="100" height="100" ... />. That's a good rule of thumb... to break sometimes ;)

In cases where the same image is used with different sizes and likely even on the same page, it may be beneficial to reuse the same bigger image and scale it down, because this could be saving extra HTTP requests of downloading the same (but slightly smaller) image.

Facebook is an example, the same hairy guy on the screenshot has two images with different sizes. It's actually the same image but resized in CSS.

The relevant CSS which shows the profile image in LARGE and SMALL (and looks like there's even a TINY view, although I couldn't find an example on this page)

.UIProfileImage_LARGE{width:50px;height:50px}
.UIProfileImage_SMALL{width:32px;height:32px}
.UIProfileImage_TINY{width:25px;height:25px}

Thank you!

Thanks for reading! Reducing HTTP requests is critical for page performance. You've merged your scripts and styles as much as reasonable, you've crafted CSS sprites and inlined images with data URIs. Now it's time to look at what's left - are there components that are way too similar, are there any near-duplicates or even exact duplicates? Same image on different backgrounds? Ever-so-subtle gradients and shadows? Time to pick up the old axe and cut.