css diff

May 9th, 2014. Tagged: CSS

CSS is the worst because it blocks page rendering. The bigger it grows, the more it blocks. And bigger is what CSS becomes if left unattended, as anyone who has worked on any project for more than a few days or with more than a few people can confirm.

So occasionally one must step back and look with disgust at what one hath done and refactor and delete, delete, delete...

But how do you know you didn't mess anything up? CSS grows out of proportion so easily because people are afraid to touch it, because they don't know what might break.

Even if not refactoring, even if you just fiddle with something on the product promo page, how do you know you didn't break the homepage by mistake?

How about you take a screenshot before and after the change and compare? And how about you let the machine compare the difference in the screenshots? And how about letting the machine compare in bulk - home page, about page... you get the point?

This post is about a quick script that shows an example of what you can do using phantomjs and imagemagick.

Setup

1. Install imagemagick (e.g. $ brew install imagemagick)
2. Install nodejs and phantomjs (npm install phantomjs)

url2png with phantomjs

First you need a little utility script that uses phantomjs to load a page and makes a screenshot, let's call it url2png.js:

var system = require('system');
var url = system.args[1];
var png = system.args[2];
 
var page = require('webpage').create();
page.viewportSize = { width: 800, height: 600 };
page.open(url, function (status) {
  if (status !== 'success') {
    console.log('Unable to access the network!');
  } else {
    page.render(png);
  }
  phantom.exit();
});

Running the script:

$ phantomjs url2png.js http://google.com goog.png

Result:

Actually the png part in url2png.js is a misnomer. You can pass a jpeg filename, a pdf... and they'll all work thanks to the magic of phantomjs.

Preparing test files

Let's test using design #1 from csszengarden - you need the CSS and the HTML that it applies to.

$ curl http://csszengarden.com > zen.html
$ curl http://csszengarden.com/001/001.css > before.css

Now make a copy of before.css and edit something, e.g. change the first property - make the margin 1px instead of 0.

$ cp before.css after.css

Finally, find the <link> tag in the zen.html and make it more template-y, so it can be scripted:

<link rel="stylesheet" media="screen" href="{{{FILE}}}">

Here's what you have so far: https://www.phpied.com/files/diffcss/source/, the three files neatly in a /source directory.

The plan

Write a script that:

  1. Reads from the /source directory
  2. Writes tmp/before.html (that <link>s to source/before.css)
  3. Writes tmp/after.html (that <link>s to source/after.css)
  4. Takes screenshots of loading both pages - tmp/before.png, tmp/after.png
  5. Runs ImageMagick's compare utility
  6. If there's a difference between the two screenshots, make an animated tmp/dif.gif with the two so it's easier to compare by humans

diffcss.js

Here's one implementation, callback hell and all:

var read = require('fs').readFileSync;
var write = require('fs').writeFileSync;
var exec = require('child_process').exec;
 
var tmp = process.cwd() + '/tmp/';
 
var html = read('source/zen.html', 'utf8');
var before = html.replace('{{{FILE}}}', '../source/before.css');
var after = html.replace('{{{FILE}}}', '../source/after.css');
 
write('tmp/before.html', before);
write('tmp/after.html',  after);
 
exec('phantomjs url2png.js "file://'+ tmp +'before.html" tmp/before.png',
  function () {
    exec('phantomjs url2png.js "file://'+ tmp +'after.html" tmp/after.png',
      function () {
        exec('compare -metric PSNR tmp/before.png tmp/after.png tmp/diff.png',
          function(e, ste, result) {
            if (result !== 'inf') {
              console.log('bad, bad! See tmp/dif.gif');
              exec('convert -delay 50 -loop 0 ' +
                   'tmp/before.png tmp/after.png '+
                   'tmp/dif.gif');
            } else {
              console.log('all good');
            }
          }
        );
      }
    );
  }
);

The imagemagick compare part (and the gif command) I lifted off from http://www.imagemagick.org/Usage/compare/. Here's what it has to say about the metric I chose:

PSNR .... Peak Signal to noise ratio (used in image compression papers)
The ratio of mean square difference to the maximum mean square
that can exist between any two images, expressed as a decibel
value.

The higher the PSNR the closer the closer the images are, with
a maximum difference occurring at 1. A PSNR of 20 means
differences are 1/100 of maximum.

When the two images are identical I get "inf" (short for "infinity" I suppose). If not, I consider this a failed test and proceed to announcing that fact together with creating that cute little gif.

Results

Check them out here: https://www.phpied.com/files/diffcss/tmp/, animated GIF and all.

Other options

You see it's not hard to roll your own CSS diff helper/checker/unit testing utility. But there are also readily available other options, see for example PhantomCSS. This page also links to other existing tools.

Another thing is that you can also do the image diffing yourself (here's a php example) and make it run faster by bailing out as soon as one pixel is different. No need to compare the rest.

Happy diffing

Now let's bravely embark on cutting down some o' that CSS bloat! Arrrr!

UPDATE: Oh lookie - part 2

Tell your friends about this post on Facebook and Twitter

Sorry, comments disabled and hidden due to excessive spam.

Meanwhile, hit me up on twitter @stoyanstefanov