Archive for the ‘Programming’ category

CSSMin updated

2011-01-13 – 9:56am

I’ve updated my CSSMin project with a couple of new features and bugfixes. Download or fork it on GitHub!

What is it?

CSSMin takes your CSS file and strips out everything that’s not needed — spaces, extra semicolons, redundant units, and so on. That’s great, but there are loads of programs that do that. A shell script could do that! So what makes CSSMin different?

When you deliver content over the web, best practice is to deliver it gzipped. CSSMin takes your CSS file, and optimises it for gzip compression. This means that there’s a smaller payload delivered over the wire, which results in a faster experience for your users. It does this optimisation by ensuring that the properties inside your selectors are ordered consistently (alphabetically) and in lower case. That way, the gzip algorithm can work at its best.

What this means in practice is that your gzipped CSSMin-ed files are significantly smaller than plain gzipped CSS, and noticeably smaller than files that have been compressed by other means (say, YUI).

In this update:

Nested properties are now fully supported.

This means that the following CSS:

@-webkit-keyframes 'fadeUp' {
  from { opacity: 0; }
  to { opacity: 1; }
}

is compressed down to

@-webkit-keyframes 'fadeUp'{from{opacity:0}to{opacity:1}}

Your nested properties will have their contents compressed with all of the other tricks in system, but their order will be retained.

Thanks to bloveridge for reporting this bug and verifying the fix.

Font weights are replaced by their numeric counterparts.

.alpha {
  font-weight: bold;
}
.beta {
  font-weight: normal;
}

becomes

.alpha{font-weight:700}.beta{font-weight:400}

Values supported are “lighter”, “normal”, “bold”, and “bolder”.

Quotes are stripped where possible.

.alpha {
  background: url('ponies.png');
  font-family: 'Times New Roman', 'Arial';
}

becomes

.alpha{background:url(ponies.png);font-family:'Times New Roman',arial}

As much text as possible is changed to lower-case.

Only selectors, quoted strings (such as ‘Times New Roman’) and url() values are left intact.

Note that this means that if you mix the case of your selectors (for example, SPAN and span), your compression will be sub-optimal.

Thanks

Some of the ideas for this update were inspired by Lottery Post’s CSS Compressor.

Start using it!

Requirements

You will need a recent version of Java and the Java compiler.

Download

Download or fork it on GitHub.

Usage

  1. Compile the Java:
    # javac CSSMin.java
  2. Run your CSS through it:
    # java CSSMin [input] [output]

If you don’t specify an output file, the result will be dumped to stdout. Warnings and errors are written to stderr.

Results

These are the results of compressing the main CSS file for one of the webapps I develop at work. Note that many of these compressors only offer an online service, which means that they can’t easily be used as part of your general build process.

  Original size (bytes) Gzipped size (bytes)
Plain 81938 12291
YUI 64434 10198
LotteryPost 63609 10165
CSS Drive 69275 10795
CSSMin 63791 9896

Feedback

Let me know how you go with it — bug reports and feature requests are always welcome!

Theme update (again!)

2010-08-23 – 1:12pm

And so, for the third time this year, I’ve completely re-themed this site. Taking a very different tack from the last theme, I’ve tried to keep this one as simple as possible, with just a few subtle touches here and there to add interest. The palette is more subdued, which hopefully means that reading the text is a more pleasant experience. I’ve also done away with the multi-column body text in favour of a fixed-width design.

At the same time, though, I have endeavoured to use some of the new features available in modern web browsers — gradients, shadows, transitions, generated content, and so on. I’m fairly happy with the result — I think it’s a clean, unobtrusive theme that’s not too in your face. Feedback and criticism welcome! :D

Paired sort in JavaScript

2010-08-19 – 10:42am

Sometimes, you’ll have two arrays of associated data, and you’ll need to sort them. You can’t just call sort() on both arrays, because that will potentially break the associations between them. What you need is a way to sort one of the arrays, and shuffle the elements of the second array to match. Here’s a simple MooTools function that does just that (using quicksort):

Array.implement({
	pairedSort: function(other, reverse) {
		if (this.length === other.length) {
			for (var i = 0, len = this.length; i < len; i++) {
				var curr = this[i];
				var currOth = other[i];
				var j = i - 1;
				while ((j >= 0) && (this[j] > curr)) {
					this[j + 1] = this[j];
					other[j + 1] = other[j];
					j--;
				}
				this[j + 1] = curr;
				other[j + 1] = currOth;
			}
		}
		if (reverse) {
			this.reverse();
			other.reverse();
		}
		return this;
	}
});

Using this function is quite straightforward:

var alpha = [3,2,1,6,5,4];
var beta = [1,2,3,4,5,6];
alpha.pairedSort(beta);
// alpha == [1,2,3,4,5,6]
// beta == [3,2,1,6,5,4]

When would this actually be useful? Let’s say you’ve got an object which maps numeric assessment scores to an alphabetic grade:

var mapping = {
  'A': 80,
  'B': 60,
  'C': 40,
  'D': 20,
  'E': 0
}

Unfortunately, objects in JavaScript have no intrinsic order on their keys — this is because they’re essentially just hashmaps. What we need to do to be able to make use of these data, then, is create an array of keys, and one of values, and then sort them. The pairedSort() function allows us to do this easily. (In fact, it’s for this exact application that I wrote the method!)

JS1K: Swarms

2010-08-13 – 12:35pm

In the spirit of the demoscene, the JS1K competition is designed to make people push their boundaries. The competition requires entrants to write an interesting, appealing demo in JavaScript, using no more than 1024 characters (hence the name).

Of course, I had to have a shot at it. (See the result!) I decided to revisit one of my favourite toy projects: my swarms. What I came up with is this (linebreaks added for convenience):

a=Math,b=a.random,c=[],j=document.getElementById("c"),k=j.getContext("2d"),m=window,n=(W=m.innerWidth)/2,q=(H=m.innerHeight)/2,r=M=1;function s(o,p,u,v){k.fillStyle=o;k.beginPath();k.arc(p,u,v,0,a.PI*2,0);k.closePath();k.fill()}function t(o,p){for(g=40;g--;)if(!c[g]){c[g]={x:o||n,y:p||q,d:o?b()*4-2:0,e:p?b()*4-2:0,b:1,a:0,c:b()>.5};return}}with(j.style){position="fixed";top=left=0}j.width=W;j.height=H;for(f=40;f--;)c[f]=0;for(f=9;f--;)t(); m.setInterval(function(){k.fillStyle="rgba(9,9,9,.2)";k.fillRect(0,0,W,H);for(f=40;f--;)if(d=c[f])if(d.a>1E3)c[f]=0;else{l=d.a/1E3;h=l*7;d.d+=b()*.1*(n-d.x>1?1:n-d.x<-1?-1:n-d.x);d.e+=b()*.1*(q-d.y>1?1:q-d.y<-1?-1:q-d.y);d.x+=d.d;d.y+=d.e;d.a++;i=d.c?"rgba(49,153,255,":"rgba(255,49,166,";h=1-l;s(i+h+")",d.x,d.y,1+l*12);s(i+h/2+")",d.x,d.y,1+l*16);d.b&&d.b--;if(d.b==0&&d.a>250&&d.a<750&&b()>.9)for(g=40;g--&&!d.b;)if((e=c[g])&&e!=d&&e.a>=d.a){i=a.sqrt(d.x-e.x,d.y-e.y);if(i>-5&&i<5&&d.c!=e.c){t(d.x, d.y);d.b=e.b=10}}}r=n>W||n<0?-r:r;M=q>H||q<0?-M:M;n+=r;q+=M},40);

You can see the submission here: http://js1k.com/demo/270. To reduce the size, I first coded tightly, ran the uncompressed code through Google’s Closure Compiler, then hand-tweaked the result to save a few more bytes. I’m quite pleased with the result! For the curious, the full, uncompressed, commented source is below:

/* SWARMS (v1.7)
 * by Barry van Oudtshoorn (www.barryvan.com.au)
 * A re-implementation of http://www.barryvan.com.au/demos/swarms/swarms.html,
 * which in turn is a reimplementation of a Processing script that I wrote.
 *
 * Male and female particles move around a central point, gradually aging.
 * When they reach maturity, they may interact with a particle of the opposite
 * sex to spawn a new particle. As they grow old, they become larger and slower
 * until they eventually die.
 *
 * Revisions (really, these version numbers are all but meaningless!)
 * 1.0 (1023B)
 *     - Initial code.
 * 1.5 (1017B)
 *     - Moved to Closure compiler to reduce size
 *     - Some slight tweaks to particle movement
 *     - Canvas now positioned to get rid of annoying border (try fullscreen!)
 * 1.7 (1011B)
 *     - The swarm centre now moves around (simple bouncing). This allows the
 *         particles to pick up a bit more speed, and move in more interesting ways;
 *         where before they tended to just 'bubble' in the middle of the screen,
 *         the swarm, as an entity, now appears more fluid and organic.
 *     - Bugfix to spawning: particles now actually have to be near to each other
 *         in BOTH axes to spawn, and both particles have to be of suitable age. This
 *         prevents billions of particles from spawning from a single one.
 *     - Reduced the total number of particles for aesthetic reasons
 *     - Did some more "by-hand" optimisations after running it through the closure
 *         compiler, like removing global variable declarations when I'm not
 *         instantiating the variables there and then and removing the leading 0 on
 *         decimal values to conserve precious bytes. With each revision, despite
 *         adding functionality each time, the size is going down!
 *
 * Copyright 2010 Barry van Oudtshoorn.
 */

var z = function(a, b, c) { // Normalise a to between b and c
    return (a / (c - b));
},
h = function(a, b, c) { // Limx a to between b and c
    return (a > b ? b : (a < c ? c : a));
},
// I strip out all of the uninitialised declarations after compilation;
// they're globals anyway. I know it's not stylish, but it saves bytes!
r,s, // Particles that we're currently interested in
i,j, // Iterators
u,d, // Working variables
m = Math, // Alias
v = m.random, // Alias
p = [], // The particles
e = document.getElementById('c'), // The canvas element
t = e.getContext('2d'), // The context
n, // The normalised age of a particle
Q = window, // Alias
X = (W = Q.innerWidth)/2, // The swarm's COG
Y = (H = Q.innerHeight)/2,
N = M = 1, // The components of the velocity of the swarm's COG
q = function(a, b, c, f) { // Draw a blob
    t.fillStyle = a;
    t.beginPath();
    t.arc(b, c, f, 0, m.PI*2, 0);
    t.closePath();
    t.fill();
},

x = function() { // Global iterator
    //t.globalCompositeOperation = 'source-over'; // This is prettier, but "globalCompositeOperation" takes too many bytes!
    t.fillStyle = 'rgba(9,9,9,.2)';
    t.fillRect(0,0,W,H);
    //t.globalCompositeOperation = 'lighter';

    for (i = 40; i--;) { // Loop over all of the particles
        if (r = p[i]) {
            if (r.a > 1E3) {
                p[i] = 0; // Kill off old particles; use 0 because it's shorter than false, null, or using delete.
            } else {
                // Move the particle
                n = z(r.a, 0, 1E3);
                u = n * 7;
                r.X += v() * .1 * h(X - r.x, 1, -1); // Update the particle's velocity.
                r.Y += v() * .1 * h(Y - r.y, 1, -1);
                r.x += r.X; // Update the particle's coordinates based on its velocity.
                r.y += r.Y;
                r.a++; // Increment the particle's age

                // Draw the particle

                d = r.s ? 'rgba(49,153,255,' : 'rgba(255,49,166,'; // The colour of the particle
                u = 1 - n; // The opacity of the particle; n is the normalised age
                q(d + u + ')', r.x, r.y, 1 + n * 12); // Draw the primary particle blob
                q(d + (u/2) + ')', r.x, r.y, 1 + n * 16); // We draw a lighter, larger blob for a 'glow' effect

                r.f && r.f--; // If we've spawned, decrement the cooldown.

                if (r.f == 0 && r.a > 250 && r.a < 750 && v() > 0.9) { // If we're the right age, check if we can spawn!
                    for (j = 40; j-- && !r.f;) { // We have to loop over the particles again, and see if we're close enough to spawn.
                        if ((s = p[j]) && s != r && s.a >= r.a) {
                            d = m.sqrt(r.x - s.x, r.y - s.y); // Two if statements to avoid this calculation if possible
                            if (d > -5 && d < 5 && r.s != s.s) { // Collision detection? What's that?
                                y(r.x, r.y); // Spawn a new particle!
                                r.f = s.f = 10; // Enforce the spawning cooldown
                            }
                        }
                    }
                }
            }
        }
    }

    // Bounce the swarm's COG around the canvas
    N = (X > W || X < 0 ? -N : N);
    M = (Y > H || Y < 0 ? -M : M);
    X += N;
    Y += M;
},

// Spawn a new particle. If sx and sy are defined, they will be its initial
// coordinates; otherwise, we just the COG. If sx and sy are defined, we also
// give the particle a random velocity; this means that they're 'flung out'
// from their parents; at startup, though, we want them all nicely bunched.
y = function(sx,sy) {
    for (j = 40; j--;) {
        if (!p[j]) {
            p[j] = {
                x: sx || X, // The particle's coords
                y: sy || Y,
                X: sx ? v() * 4 - 2: 0, // The particles velocity
                Y: sy ? v() * 4 - 2 : 0,
                f: 1, // Cooldown after spawning
                a: 0, // The age of the particle
                s: v() > .5 // The sex of the particle
            }
            return;
        }
    }
};

// Make the canvas fill the entire screen
with (e.style) {
    position = 'fixed';
    top = left = 0;
}
e.width = W; // Set the canvas width and height
e.height = H;

for (i = 40; i--;) p[i] = 0; // Populate initial particles
for (i = 9; i--;) y(); // Spawn eight particles. A number < 10 saves a byte!

Q.setInterval(x, 40); // Make it go.