Archive for the 'CSS' Category

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

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

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

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

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

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

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.

 

Data URIs, MHTML and IE7/Win7/Vista blues

Monday, December 7th, 2009

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

Let's start this post as a dialog:

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

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

Data URIs

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

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

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

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

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

Data URIs in the wild

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

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

yahoo search screenshot

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

Google search screenshot

Base64-encoded filesizes

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

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

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

MHTML

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

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

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

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

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

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

A few notes on this example:

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

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

The problem with IE7 on Vista (and Win7)

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

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

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

In other words have something like:

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

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

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

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

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

Time to celebrate? Almost.

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

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

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

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

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

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

DataSprites class

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

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

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

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

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

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

UPDATE: Added a hover example.

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

Thanks!

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

 

Reducing the number of page components

Saturday, December 5th, 2009

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

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

Fewer components vs. component weight

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

Why? Because every HTTP request has overhead.

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

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

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

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

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

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

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

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

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

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

The bigger problem is the HTTP connection overhead.

HTTP connection overhead

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

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

And the color legend:

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

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

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

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

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

What does all this tell us?

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

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

Just remove stuff

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

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

Combine components

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

JavaScript example (from a page to remain anonymous)

Before:

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

After:

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

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

Repeat for CSS. Before:

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

After:

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

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

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

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

... 15 image requests, 6.8K

After: (1 sprited image)

Result size: 1.4K, 7 times smaller!

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

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

x-type component concatenation

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

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

And you can inline CSS and JS inside HTML too.

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

Combining CSS with JS

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

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

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

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

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

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

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

3. You add the styles to the head:

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

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

More to reducing components?

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

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

Summary

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

 

Statsy - more data points for markup quality

Monday, November 30th, 2009

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

Install the statsy bookmarklet

Drag this link to your bookmarks:

statsy

the results

Once you run the bookmarklet it alerts these stats points:

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

Here's example output:
statsy bookmarklet output

The code

The code is here for your tweaking pleasure

Thanks!

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

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

 

Flipity flop - mmm.phpied.com

Thursday, November 19th, 2009

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

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

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

mmm.phpied.com

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

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

how-to

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

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

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

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


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

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

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

And voila - mmm.phpied.com

 

@font-face gzipping - take II

Tuesday, October 20th, 2009

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

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

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

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

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

TTF to WOFF

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

OTF to WOFF

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

Gzip your @font-face files

Saturday, October 10th, 2009

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

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

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

Quick background

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

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

body {
    font-family: gzipper;
}

What's going on here?

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

So that leaves us with 4 different types of files:

  • svg
  • ttf
  • otf
  • eot

How about gzipping these?

SVG

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

TTF

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

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

On average - 41.48% savings.

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

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

OTF

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

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

EOT

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

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

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

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

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

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

Summary

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

links

 

CSS munging - a FAILed experiment

Saturday, October 3rd, 2009

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

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

the big idea

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

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

Combining lazy-loaded assets

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

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

A failed experiment

(psst, demo here)

You start with something like:

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

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

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

]

Then from that structure you produce 4 arrays that contain:

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

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

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

Results

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

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

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

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

cssmin.js

Wednesday, September 23rd, 2009

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

The motivation

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

The links

The integration

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

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

The credits

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

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

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

 

Sorting CSS to reduce gzip file sizes?

Wednesday, September 2nd, 2009

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

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

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

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

How it works

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

Before:

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

a {
  a: a;
}

After

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

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

Help by testing your CSS

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

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

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

Results so far?

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

 

The art and craft of postload preloads

Thursday, August 27th, 2009

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

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

One way... (DOM)

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

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

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

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

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

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

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

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

    }, 1000);
};

DOM test page

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

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

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

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

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

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

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

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

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

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

    }, 1000);
};

IFRAME test page

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

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

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

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

    }, 1000);
};

IFRAME static page test

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

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

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

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

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

    }, 1000);
};

Ajax test page

Thanks!

Any other ways you can think of?

 

The star hack in IE8 and dynamic stylesheets

Friday, July 3rd, 2009

CSS hacks

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

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

Consider this:

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

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

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

Surprise

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

When you set styles dynamically IE8 behaves as IE7.

:( , :( and double :(

» Here's a demo/test page.

The code:

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

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

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

 

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

Friday, April 10th, 2009

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

MHTML-a-what?

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

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

MHTML example

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

The overall "template" would look like so:

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

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

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

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

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

Demo example with more parts

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

The HTML is noting special and the CSS goes like:

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

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

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

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

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

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

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

Drawback

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

 

>>> $$(selector)

Friday, January 30th, 2009

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

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

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

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

Lots of fun!

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

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

Background repeat and CSS sprites

Thursday, April 3rd, 2008

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

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

Example

» Demo

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

Source images:

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

Singe image sprite:

sprite8.png

CSS definitions to make this work:

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

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

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

The tree divs have IDs: my, my2, my3

Result

Once again, the result

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

 

The Front-end Cerberus

Thursday, October 25th, 2007

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

frontend-cerberus.jpg

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

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

 

Quick CSSSprites.com stats

Wednesday, September 12th, 2007

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

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