Archive for the 'CSS' Category

CSS animations off the UI thread

Tuesday, March 12th, 2013

This excellent Google I/O talk mentions that Chrome for Android moves the CSS animations off of the UI thread, which is, of course, a great idea. Playing around with it, here's what I found:

  • Browser support: Desktop Safari, iOS Safari, Android Chrome.
  • You need to use CSS transforms. Animating regular properties doesn't work.

Update: (see comments) confirmed support in IE10. Reported support in Firefox OS too, but I cannot personally confirm

More details and test page below.

Single UI thread

As you probably know the browser is single threaded. Do something heavy in ECMAScript land and everything freezes.

The big idea

CSS animations should be excluded from the "everything" that freezes.

Test page

Here's a test page with some animations. Click the kill button and see what happens.

Animations

The red box that spins is animated like:

.spin {
  animation: 3s rotate linear infinite;
}
 
@keyframes rotate {
  from {transform: rotate(0deg);}
  to {transform: rotate(360deg);}
}

The green one is also animated with a transform:

.walkabout-new-school {
  animation: 3s slide-transform linear infinite;
}
 
@keyframes slide-transform {
  from {transform: translatex(0);}
  50% {transform: translatex(300px);}
  to {transform: translatex(0);}
}

The blue one is animated using the margin-left property, not a transform:

.walkabout-old-school {
  animation: 3s slide-margin linear infinite;
}
 
@keyframes slide-margin {
  from {margin-left: 0;}
  50% {margin-left: 100%;}
  to {margin-left: 0;}
}

Kill switch

The kill button just pegs the CPU in a infinite loop for 2 seconds:

function kill() {
  var start = +new Date;
  while (+new Date - start < 2000){}
}

Results

In non-supporting browsers, which is most of them, the kill switch kills all the animations. Business as usual.

In the supporting browsers (All Safaris and Andriod Chrome) the kill only affects the blue button, the one that animates a CSS property, as opposed to using a CSS transform. But the animations that use a transform keep on going!

Take aways

  1. Rejoice! The future is here! Drink and dance uncontrollably around the campfire!
  2. After you sober up, make sure your CSS animations use transform: where possible
  3. Keep migrating them JS animations to CSS
  4. Bug your browser vendor to support this
 

webkit css-on-demand issues

Monday, February 11th, 2013

This post brought to you via Facebook engineers Jeff Morrison and Andrey Sukhachev, who discovered and helped isolate the issue.

Use case

Think a "single page app" use case. You click a button. Content comes via XHR. But content is complex (and app is as lazy-loading as possible) and content requires extra CSS. In an external file.

Only when the external CSS arrives should the app show the content. Otherwise content will be weirdly styled.

Execution

Two "modules" (or "widgets") of the app require two different CSS files. Both modules are requested at about the same time. We listen to onload of the CSS files. Expected behavior: whenever a module and its CSS dependency arrive - show that module. Asynchronously. No one cares which module shows first, as long as they show up as soon as possible.

Experimentation

Two modules. Two CSS files. 1st CSS happens to take one second. The second CSS takes 5 seconds.

Test pages: one and two

Here's the end result. The first module is pinky, the second is yellow. All good.

Same with network panel ON:

The question is what does the user see during the ?-mark - between the first CSS is done and the second one is still loading.

Oh, and here's the load() function that runs when the user clicks the button "load" initiating the new modules to appear:

function load() {
  var these = ['class1.css.php', 'class2.css.php'];
  var classes = ['class1', 'class2'];
  var head = document.getElementsByTagName('head')[0];
  
  for (var i = 0; i < these.length; i++) {
    var url = these[i];
    var link = document.createElement('link');
    
    link.type = "text/css";
    link.rel = "stylesheet"
    link.href = url;
    link.onload = (function (i) {
      return function () {
        console.log(these[i]);
        var div = document.createElement('div')
        div.appendChild(document.createTextNode(these[i]));
        result.appendChild(div);
        div.className = classes[i];
        //s = getComputedStyle(result).height;
      }
    }(i));
    
    head.appendChild(link);
  }
 
}

Expected behavior

In FF, whenever the the first CSS is loaded, we see a new module.

Test for yourself (in Firefox)

#1 issue: "efficient" webkit

You know that browsers batch layout and paints tasks because these tend to be expensive. For example they wait for all CSS (even useless print and other @media stylesheets) to arrive and block the rendering of the page. (More on these topics: here, here)

So turns out that here webkit also waits for both CSS files to arrive before rendering anything.

You see in the console we know (in JavaScript) that CSS has arrived. But webkit (chrome, safari, mobile safari) doesn't paint anything, waiting for the second CSS. Bummer!

Issue #2: painting unstyled content

While issue #1 is just a bummer that can be done better for the progressive feedback-y user experience, #2 is a bug. This is the issue that Jeff and Andrey found and were floored.

If there's a paint going on between the two stylesheets, the browser dumps the unstyled content on the page. Ugly stuff.

This was only happening sometimes, but after forming and testing an hypothesis, I was able to distill a reproducible test case. The only change is: after the load of the first CSS, flush the rendering queue by requesting a style information. e.g.

link.onload = function () {
  s = getComputedStyle(dom).height;
}

Repro and file a bug

You can reproduce for yourself. Use chrome and try:

  1. Issue: no paint till all CSS is here
  2. Bug: unstyled paint while waiting for all CSS

I was faced with "registration wall" trying to file a webkit bug, hence this post. Someone please file a bug and feel free to use the provided test cases.

The solution, IMO, is to make webkit behave like FF. No waiting for all CSS. This solves both issues. In the worst case, at least the unstyled bug (issue #2) should be addressed.

Interim solution for web developers: inline CSS required by the module together with the module content.

Thanks for reading!

 

<style> tag to inline style=”" attrrib

Thursday, June 21st, 2012

As you may have noticed, I claim that CSS is bad for performance because:

  1. Most browsers block the very first paint until all screen CSS arrives
  2. Additionally many browsers block rendering until all non-screen (e.g.print) CSS arrives
  3. Sometimes CSS blocks downloads

See "The evil that CSS do" in CSS and the critical path for details.

CSS is the critical path to delivering any UI in the browser. Images arrive whenever, JS can be async.

So any page needs to get CSS out of the way ASAP.

Simple, highly optimized pages (e.g., e.g.) reduce CSS to the bare minimum and then shove it inline in a <style> tag.

ExCeSS

It's a fact of life that there will always be unused CSS, no matter how hard you try to reduce it. (Run PageSpeed for a proof)

Take the simplest CSS: your reset.css

It has stuff like

h1, h2 , h3, ..., abbr, blockquote{ margin:0; ....}

All the HTML tags are in there. But do you have all the tags in the page? Unlikely. So there's excess CSS even at the very base. It usually gets much worse from here. Whole features may or may not be in the page or combined in different ways, but the CSS to handle all combinations is always there, omnipresent.

To style="" attrib

I saw today that Mailchimp has this CSS inliner tool. (Because mail clients often strip <style>). It takes the <style> tags in the markup, strips them and adds style="" attributes where applicable.

I decided to give it for a spin with Facebook like and Google search's HTML. Remember: these are two already highly optimized pages.

Assuming the tool works correctly, the results were pretty impressive.

  • Like: 8,133 bytes from 10,115 (20% reduction, 23% after `gzip -9`)
  • Search: 63,508 from 90,846 (30% reduction, 27% post gzip)

I know, I know what you'll say: inline style="" is an abomination. Should we bring <font> back? What about the cascade? Is this transformation needed on every page view with dynamic content, how's that scalable? What if there's a lot of content with the same class, lot of duplicates?

I know, I know.

But, but... look at the results. 25% reduction of the HTML payload!

With web development moving more and more toward transformations and compilation (css preprocessors, coffee script, monification, etc) it may not be unthinkable.

Back to Earth

On more realistic note, just reduce the CSS to under 2K or thereabouts, inline it in the head, send it with the the first server flush (even before any data fetching) and you'll be in a good place already!

 

CSS variables

Thursday, June 14th, 2012

Weeee, CSS variables just landed in WebKit, this is pretty exciting!

Unfortunately I couldn't see them in action in WebKit nightly (must be something I'm missing), but they're here and here to stay. I know there are "purists" that say that stuff like variables has no place in the language and we don't really understand CSS, it's different, etc, etc. But once W3C has a modest proposal and one major engine is implementing variables, then this is where we're headed. And I personally applaud this direction.

Syntax

So the basic idea, according to W3C is this. There are two steps: you declare a variable (together with a value assignment) and then you use it.

This is a declaration of the variable my-color. It's a global variable (defined under :root).

:root {
  var-my-color: #fad;
}

Then you use it like:

.thing {
  color: var(my-color);
}

And the .thing gets the color #fad.

In WebKit

Webkit currently uses a -webkit vendor prefix which makes the whole thing more verbose:

:root {
  -webkit-var-my-color: #fad;
}
 
.thing {
  color: -webkit-var(my-color);
}

There are many examples for your browsing pleasure in the form of test files in WebKit's repo

Future

It's here. And it's only getting better. There's a dev draft of the CSS variables proposal which talks about using $ instead of var() to refer to a variable, which is so much nicer. Spoken like a true php'ied, eh? :)

:root {
  var-my-color: #fad;
}
 
.thing {
  color: $my-color;
}

Exciting times!

 

CSS and the critical path

Tuesday, June 5th, 2012

Back when I was still actively into speaking at public events (way, way back, something like year and a half ago (which strangely roughly coincides with the time I joined Facebook, hmmm (hmm? (huh? what's with the parentheses? sure all of them are closed at this point?)))) I remember showing this slide:

The reason I'm bringing it up now is this experiment I saw today by Scott Jehl.

media="nonsense"

Scott added LINK elements with non-applicable media attribs, such as tv, too much min-width and pixel ratio of 6 among others:

<link href="inc/tv.css" rel="stylesheet" media="tv">
<link href="inc/min-width-4000px.css" rel="stylesheet" media="(min-width: 4000px)">
<link href="inc/min-device-pixel-ratio-6.css" rel="stylesheet" 
    media="(min-device-pixel-ratio: 6)">

And just for the fun of it, why not a nonsense value:

<link href="inc/nonsense.css" rel="stylesheet" media="nonsense">

Scott observed that (with one happy Opera nonsense exception) all browsers will load all this junk, all this CSS that they don't need.

(BTW, Opera 11.64 loaded nonsense css for me too)

Blocking rendering?

Having recently remembered how browsers block rendering because of print stylesheets, I speculated that all the nonsense media will also block rendering. Unfortunately I was right.

So not only browsers download useless bytes, but they also block the rendering of the page (or block window.onload, or both) until all the crap is downloaded. And by blocked rendering I mean showing a white page of death. Most browsers wait until all CSS is loaded because they don't like doing extra layouts and painting (except Opera).

Here's a test page for you to try:

http://www.phpied.com/files/css-loading/mq.php?mq=all

Change all with your media query of choice, hit Enter and weep.

E.g.
http://www.phpied.com/files/css-loading/mq.php?mq=tv
http://www.phpied.com/files/css-loading/mq.php?mq=nonsense

This test page loads css with delay: css1 delayed 5 seconds and css2 delayed 10 seconds. The HTML is:

<link rel="stylesheet" href="css1.css.php" type="text/css" media="screen" />
<link rel="stylesheet" href="css2.css.php" type="text/css" 
    media="<?php echo $YOUR_MEDIA_QUERY; ?>" />

The correct browser behavior should be:
1. load only the CSS you need
2. render
3. fire onload

Maybe even:
0. render if step 1. takes too long

Instead, randomness ensues: Firefox treats us to a white page for 10 seconds while downloading nonsense. Chrome takes 15 seconds to fire onload. (see the print CSS post for more)

So what are we to do? First, understand...

The evil that CSS do

  1. Browsers (except Opera) block rendering until all screen CSS arrives. With the worst possible experience: white page.
  2. Browsers download CSS they don't need, e.g. print, tv, device-ratio... And most browsers (except Opera and Webkit) block rendering because of these too
  3. Sometimes CSS blocks the other downloads too (not just block rendering, but block images and scripts that follow):

The critical path

When building high-performance pages we want to stay off the critical path. Critical is the path from the user following a link to the first impression and then the working experience. That's why we load javascript asynchronously and so on.

But I argue that CSS is not only on the critical path, it is the critical path. And because it's a jungle (network, 3g, edge) out there, anything on the critical path will fail. Guaranteed.

Think about this: you have an HTML page and then you have components. Without the HTML, there is no path really. Game over. Without images? Depends on the page, but you can live without images most of the time. Without JavaScript? Well you should build the pages so the important stuff, links, forms, content works without javascript. Without webfonts? You're kidding me, I don't need no stinkin' fonts when I'm late and running to the airport and checking in for the flight on the damn phone with the spotty mobile network while Wifi wants to connect and I have to say no, because if I say yes I'll wait for another page where I have to say "I accept" and aim at a miniscule checkbox with these sweaty fat fingers or worse I have to enter usernameandpassword, and omg-omg-OMG mobile.southwest.com wants to look like native iPhone and won't let me click until mountains of JS arrive, so no, don't talk to me about no damn fonts!

What's left on the critical path is CSS. Not only the page is ugly without CSS, we can live with that, but there is no page without CSS because the browser waits and waits and takes forever to timeout showing us a blank white page.

Get the CSS out of the way

So if you worry about performance, you should get the CSS out of the way as soon as possible. Get off the critical path. Make CSS small, minify, compress, load from the same hostname even (no DNS) and inline, if small enough. Yup, inline.

Take a look at these highly optimized experiences...

Look ma, no CSS!

Yes, these pages make no CSS requests whatsoever.

If your CSS is not puny enough to be all inline (Guy has some observations on what puny means) it should at least be a single file, way at the top of the document, with the first flush. Just get it over with. Your users will love you and praise you and use words like smooth and snappy.

 

5 years later: print CSS still sucks

Wednesday, April 25th, 2012

This tweet had me revise a 5 year old experiment on how print CSS affects page loading, especially in the light of mobile browsers.

So I tweaked the test ever so slightly to print out timing info in the document.title and after the page is done.

The test is essentially how does a slow print stylesheet affect the rendering of the page on the screen.

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

In the experiment I have screen.css delayed with 5 seconds and print.css delayed 10 seconds.

Results 5 years ago

Browsers blocked rendering waiting for print.css. Some took 10 seconds (downloading print.css and screen.css in parallel), some took 15. Why, oh why? It's a print CSS, you don't need this sh...eet.

Results today

Good guy Opera, doesn't even wait for screen.css. After some timeout, O renders unstyled page and restyles after screen.css arrives. Yes, brave O takes rendering risks this way, all others wait for the screeen.css before styling anything. Still, onload fires in ~10s, so this is bad. All your onload JS code is blocked on a useless print.css

FF blocks rendering on the print.css. Boo! Nothing renders for 10 seconds. And it fires onload after ~10s. Boo-boo! At least it loads both stylesheets in parallel. Galaxy (Android) waits for print.css too. How often do you print from a mobile device? Same in IE8 and IE9. Even more retarded in IE is DOMContentLoaded event also waiting for 10 seconds. speech=less.

Safari, Chrome, Mobile Safari - render after 5 seconds, meaning only after screen.css. There is hope for the humanity. However the onload fires in 15 seconds. So the two CSS files are downloaded sequentially. Kinda makes sense, print.css is low priority and should give way to everything else. Still could start earlier if there are no other downloads competing for precious resources.

So on the wall of shame - IE worst, FF yuck, Webkit bad, O least bad.

Recommendation

Ditch media="print" if you have one! (Hey why isn't this a yslow/pagespeed rule?). Ditch it because in the best case scenario it will only block onload. In the worst case it will block initial paint, onload and DOMContentLoaded. Sitting in front of a white page with no feedback is the worst possible user experience.

Put all (should be minimal anyway) print rules inline in your normal screen stylesheet.

@media print {
  body {font: fit-to-print}
  .big-ads, .sidebar, .menu {display: none}
}
 

A rounded corner

Wednesday, April 13th, 2011

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

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

But first:

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

Demo

Live page demo and a screenshot in IE6:

IE rounded corner

Sales pitch

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

The drawbacks later.

The big idea

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

Implementation

Markup

As promised, nothing to see here:

<p>Hello world</p>

JavaScript

None. *

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

CSS

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

The expression goes like:

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

(Expression stolen from Thierry, btw)

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

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

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

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

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

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

And this is it.

Drawbacks

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

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

So there

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

 

When is a stylesheet really loaded?

Thursday, March 17th, 2011

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

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

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

Here's what the standard says:

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

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

// my callback function
// which relies on CSS being loaded
function CSSDone() {
  alert('zOMG, CSS is done');
}
  
 
// load me some stylesheet
var url = "http://tools.w3clubs.com/pagr/1.sleep-1.css",
    head = document.getElementsByTagName('head')[0];
    link = document.createElement('link');
 
link.type = "text/css";
link.rel = "stylesheet"
link.href = url;
 
// MAGIC
// call CSSDone() when CSS arrives
 
head.appendChild(link);

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

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

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

So how about implementing the magic?

  // MAGIC
  // #1
  link.onload = function () {
    CSSDone('onload listener');
  }
  // #2
  if (link.addEventListener) {
    link.addEventListener('load', function() {
      CSSDone("DOM's load event");
    }, false);
  }
  // #3
  link.onreadystatechange = function() {
    var state = link.readyState;
    if (state === 'loaded' || state === 'complete') {
      link.onreadystatechange = null;
      CSSDone("onreadystatechange");
    }
  };
  
  // #4
  var cssnum = document.styleSheets.length;
  var ti = setInterval(function() {
    if (document.styleSheets.length > cssnum) {
      // needs more work when you load a bunch of CSS files quickly
      // e.g. loop from cssnum to the new length, looking
      // for the document.styleSheets[n].href === url
      // ...
      
      // FF changes the length prematurely :( )
      CSSDone('listening to styleSheets.length change');
      clearInterval(ti);
      
    }
  }, 10);
  
  // MAGIC ends

Test

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

Results

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

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

Is there really no hope for Firefox?

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

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

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

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

Takeaways

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

UPDATE: FF solved!

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

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

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

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

Updated my test and seems to work just fine.

Whew!

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

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

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

 

Command-line CSS spriting

Saturday, February 19th, 2011

(In Russian)

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

Creating the image

Starting with a list of separate image files:

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

Creating the sprite:

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

Yes, that's all! The result:

What?

So the imagemagick command is generally something like:

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

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

$ convert * -append result-sprite.png

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

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

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

The result:

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

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

CSS positions

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

And some smushing

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

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

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

Result is JSON:

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

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

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

And that's that.

Recap

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

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

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

 

CSS railroad diagrams

Sunday, November 28th, 2010

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

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

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

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

Stylesheet

stylesheet css railroad diagram

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

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

@charset

@charset css railroad diagram

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

E.g.

@charset "UTF-8";

@import

@import css railroad diagram

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

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

Ah, media query syntax, lovely.

@namespace

@namespace css railroad diagram

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

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

@keyframe

@keyframe css railroad diagram

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

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

@media

@media css railroad diagram

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

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

Media query

media query css railroad diagram

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

aural
braille
handheld
print
projection
screen
tty
tv
embossed
speech
all

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

So, simple:

@media print {...}

Or:

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

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

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

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

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

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

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

Media expression

media expression css railroad diagram

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

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

The feature can be one of:

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

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

Ruleset

ruleset css railroad diagram

Whew, finally something that looks normal - the old:

#mydiv {
  color: red
}

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

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

Block

block css railroad diagram

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

Declaration

declaration css railroad diagram

The plain old property: value pairs.

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

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

@font-face

@font-face css railroad diagram

The @font-face. Nothing overly exciting actually.

@page and page blocks

@page css railroad diagram
page block css railroad diagram

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

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

@media print {
  @page mine :left {
    margin: 1cm;
    @bottom-left-corner {
      margin: 2cm;
    }
  }
}
 
/* or something simpler */
@page {
  size: A4 landscape;
}

The page margins allowed are:

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

The pseudo page values allowed are

:left
:right
:first

Thanks

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

Thanks for reading and looking forward to any mistake pointers!

 

CSS Lexer

Saturday, November 27th, 2010

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

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

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

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

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

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

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

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

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

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

 

CSS minifiers comparison

Saturday, October 30th, 2010

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

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

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

Experiment

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

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

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

Lastly, Ajaxmin I ran with the default options:

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

Results

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

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

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

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

Detailed results

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

cssmin.js in windows shell

Friday, October 29th, 2010

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

Say you have a JavaScript function foo()

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

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

(function () {
 
    var input  = WScript.StdIn.ReadAll(),
        output = foo(input);
 
    WScript.StdOut.Write(output);
 
}());

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

$ cscript foo.js < input.txt

And it prints the output to the console.

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

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

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

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

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

CSSMin.JS

Alrighty, going back to the title of the post.

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

(function () {
 
    var src = WScript.StdIn.ReadAll();
 
    if (!src) {
        // help!
        WScript.StdOut.WriteLine("cscript //NoLogo cssmin.js < in.css > out.css");
        return;
    }
 
    WScript.StdOut.Write(YAHOO.compressor.cssmin(src));
 
}());

Download it here.

Use it like:

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

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

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

 

Inline MHTML+Data URIs

Sunday, October 3rd, 2010

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

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

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

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

So it's a simple HTML doc:

<!doctype html>
<html>
  <head>
    <title>Look Ma' No HTTP requests</title>
    <style type="text/css">
 
/* magic here */
 
    </style>
  </head>
  <body>
    <h1>MHTML + Data:URIs inline in a <code>style</code> element</h1>
    <p class="image1">hello<br>hello</p>
    <p class="image2">bonjour<br>bonjour</p>
  </body>
</html>

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

/*
Content-Type: multipart/related; boundary="_"
 
--_
Content-Location:locoloco
Content-Transfer-Encoding:base64
 
iVBORw0KGgoAAAAN ... [more crazyness]... QmCC
--_
Content-Location:polloloco
Content-Transfer-Encoding:base64
 
iVBORw0KGgoAAAANSUh ... [moarrr] ... ggg==
--_--
*/
.image1 {
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAAN ... QmCC"); 
  *background-image: url(mhtml:http://phpied.com/files/mhtml/mhtml-html.html!locoloco); 
}
 
.image2 {
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUh ... ggg=="); 
  *background-image: url(mhtml:http://phpied.com/files/mhtml/mhtml-html.html!polloloco); 
}
 
body {
  font: bold 24px Arial;
}

How cool is that!

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

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

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

 

The proper MHTML syntax

Sunday, October 3rd, 2010

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

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

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

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

So let's take a look at the syntax.

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

One part

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

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

iVBORw0KGgoAAAANSU....U5ErkJggg==

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

Multi parts

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

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

--MYSEPARATOR

[here comes part one]

--MYSEPARATOR

[here's part two]

--MYSEPARATOR--

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

All together now

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

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

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

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

Updated PHP class

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

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

Updated test pages

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

 

Conditional comments block downloads

Sunday, May 23rd, 2010

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

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

Check out the tests.

Base page

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

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
   "http://www.w3.org/TR/html4/strict.dtd">
    
<html>
<head>
    <title>The base page</title>
    <link type="text/css" rel="stylesheet" 
          href="http://tools.w3clubs.com/pagr/1.expires.css">
</head>
<body>
<p>
    <img src="http://tools.w3clubs.com/pagr/1.expires.png" alt="1">
    <img src="http://tools.w3clubs.com/pagr/2.expires.png" alt="2">
    <img src="http://tools.w3clubs.com/pagr/3.expires.png" alt="3">
    <img src="http://tools.w3clubs.com/pagr/4.expires.png" alt="4">
</p>
<script type="text/javascript" 
        src="http://tools.w3clubs.com/pagr/1.expires.js"></script>
</body>
</html>

The waterfall produced by WebPageTest looks like so:

base page

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

Conditional IE6 stylesheet

Adding a second stylsheet to the head like so:

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

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

Test page, test results, waterfall:

CC style page

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

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

Conditional markup

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

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

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

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

Browser-sniffing comments

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

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

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

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

X-UA-Compatible not a solution

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

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

It didn't work for me.

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

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

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

Conclusions, conclusions

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

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

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

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

Update: empty conditional comment

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

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

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

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

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

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

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
   "http://www.w3.org/TR/html4/strict.dtd">
<!--[if IE 6]><![endif]-->
 
<html>
<head>
  <title>base page</title>
  <link type="text/css" rel="stylesheet" 
        href="http://tools.w3clubs.com/pagr/1.expires.css">
  <!--[if IE 6 ]>    
    <link type="text/css" rel="stylesheet" 
          href="http://tools.w3clubs.com/pagr/2.expires.css">
  <![endif]-->
</head>

Test page, webpagetest results. No more blocking :)

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

Test page, results, code:

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

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

 

YUI CSS min – part 3 – hacks

Friday, May 21st, 2010

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

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

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

Underscore/star hack

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

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

Before:

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

After:

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

Child selector hack

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

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

Before:

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

After:

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

IE5/Mac hack

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

Before:

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

After:

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

Box model hack

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

Before:

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

After:

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

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

That's all, folks!

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

 

YUI CSS Min – part 2

Thursday, May 20th, 2010

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

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

Stripping comments and white space

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

So before:

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

After:

.classmates{weight:considerable}

Special comments

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

Before:

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

After:

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

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

Striping last semi-colon

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

Before:

a {
  one: 1;
  two: 2;
}

After:

a{one:1;two:2}

Extra semi-colons

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

Before:

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

After:

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

No empty declarations

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

Before:

.empty { ;}

After:

(nothing...)

Zero values

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

Before:

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

After:

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

Floats

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

Before:

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

After:

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

Colors values

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

Before:

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

After:

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

Before:

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

After (no color minification) :

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

Single charsets

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

Before:

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

After:

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

Alpha opacity

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

So before:

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

After:

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

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

Thanks!

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

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

 

15 minutes could save you…

Monday, May 17th, 2010

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

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

gzip on

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

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

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

Flush

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

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

One at the top:

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

And one at the bottom:

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

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

favicon.ico

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

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

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

Minifying CSS

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

Cover images

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

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

And just like that - 4 book covers were optimized.

Before/after

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

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

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

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

And the waterfalls. Before:

After:

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

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

 

Browser sniffing with conditional comments

Thursday, May 13th, 2010

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

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

Conditional comments in practice use the following pattern:

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

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

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

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

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

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

The highlighting suggests what the decent browsers see.

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

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

Preload CSS/JavaScript without execution

Wednesday, April 21st, 2010

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

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

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

Code and demo

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

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

window.onload = function () {
 
    var i = 0,
        max = 0,
        o = null,
 
        // list of stuff to preload
        preload = [
            'http://tools.w3clubs.com/pagr2/<?php echo $id; ?>.sleep.expires.png',
            'http://tools.w3clubs.com/pagr2/<?php echo $id; ?>.sleep.expires.js',
            'http://tools.w3clubs.com/pagr2/<?php echo $id; ?>.sleep.expires.css'
        ],
        isIE = navigator.appName.indexOf('Microsoft') === 0;
 
    for (i = 0, max = preload.length; i < max; i += 1) {
        
        if (isIE) {
            new Image().src = preload[i];
            continue;
        }
        o = document.createElement('object');
        o.data = preload[i];
        
        // IE stuff, otherwise 0x0 is OK
        //o.width = 1;
        //o.height = 1;
        //o.style.visibility = "hidden";
        //o.type = "text/plain"; // IE 
        o.width  = 0;
        o.height = 0;
        
        
        // only FF appends to the head
        // all others require body
        document.body.appendChild(o);
    }
    
};

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

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

Comments

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

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

Other unsuccessful attempts

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

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

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

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

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

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

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

In conclusion

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

Thanks for reading!

 

Publishing 5 books this year

Thursday, April 1st, 2010

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

Book #1 - High-Performance JavaScript

hpjs

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

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

Book #2 - JavaScript Patterns

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

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

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

Book #3 - Speed Matters

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

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

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

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

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

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

Hoping this title will not take a lot of time.

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

Book #5 - CSS for web devs

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

Too ambitious? April Fool's?

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

 

YUICompressor’s CSSMin

Wednesday, March 10th, 2010

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

PHP, Java and a JavaScript port

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

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

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

Page Speed

YSlow

Building

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

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

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

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

Tests

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

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

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

The procedure to write new tests is simple:

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

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

Next time

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

 

Uncompressed data in base64? Probably not

Thursday, February 4th, 2010

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

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

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

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

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

When using data URIs you essentially do this:

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

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

Uncompressed PNG?

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

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

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

Code

<?php
// images to work with
$images = array(
  'html.png',
  'at.png',
  'app.png',
  'engaged.png',
  'button.png',
  'pivot.png'
);
//$images[] = 'sprt.png';
//$images[] = 'goog.png';
//$images[] = 'amzn.png';
//$images[] = 'wiki.png';
 
// css strings to write to files
$css1 = "";
$css2 = "";
 
foreach ($images as $i) {
  
  // create a "d" file, d as in decompressed
  copy($i, "d$i");  
  $cmd = "pngout -s4 -force d$i";
  exec($cmd);
  
  // selector
  $sel = str_replace('.png', '', $i);
 
  // append new base64'd image 
  $file1 = base64_encode(file_get_contents($i));
  $css1 .= ".$sel {background-image: url('data:image/png;base64,$file1');}\n";
  $file2 = base64_encode(file_get_contents("d$i"));
  $css2 .= ".$sel {background-image: url('data:image/png;base64,$file2');}\n";
 
}
 
// write and gzip files
file_put_contents('css1.css', $css1);
file_put_contents('css2.css', $css2);
exec('gzip -9 css1.css');
exec('gzip -9 css2.css');
 
?>

Results

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

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

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

 

One-click Minifier Gadget (OMG) – initial checkin

Sunday, January 31st, 2010

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

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

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

Running

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

OMG screenshot

Download

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

Open source

The code is on GitHub. Fork and enjoy.

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

Next?

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

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

Built with XUL

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

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