nooshu - Matt Hobbs' Web Development Blog

Kneeling on the shoulders of giants

Category: Experiments

I’m constantly thinking of little projects to be working on to expand my knowledge and skill-set, posts relating are filed here.

JS1k: Original source

Unfortunately I haven’t been able to put together another JS1k demo as I’ve had lots of ‘real’ work going on at the moment (boo!); so I thought I’d post a fully commented version of the JavaScript code for anyone who is interested to look over.

Some of the entries have been breathtaking, making my poor little demo look a little simple! Never mind. I look forward to looking over the uncompressed source of the other entries. There are just so many things to learn from them!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/**
 * This uncompressed source is close to my entry submitted to JS1k.
 * The number of particles and colours are different, but the rest is
 * near enough the same. The code is structured in a much neater way.
 */

var animate = function(){
    //Grab our canvas object and context
    var canvas = document.getElementById('c');
    var c = canvas.getContext('2d');
   
    //Set the canvas width to the same as the browser
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
   
    //Make the body background black and hide the scroll bars
    document.body.style.background = "black";
    document.body.style.overflow = "hidden";
   
    //Grab the width / height of the canvas for later use
    var width = canvas.width,
    height = canvas.height;
   
    //Arrays used to store the position / speed / direction and radius of the particles
    var posY = [],
    posX = [],
    speedX = [],
    speedY = [],
    directionX = [],
    directionY = [],
    radius = [];
   
    return {
        //Our initialise function
        init: function(){
            //Look at the width and generate a number of particles
            for(i = 10; i < width; i += 30) {
               
                //For each ballplace it in a random location
                var generatedX = Math.floor(Math.random()*width);
                var generatedY = Math.floor(Math.random()*height);
               
                //Set the initial particle start positions
                posX[i] = generatedX;
                posY[i] = generatedY;
               
                //Each particle has a random x and y speed
                var generatedSpeedX = 1 + Math.ceil(Math.random()*8);
                var generatedSpeedY = 1 + Math.ceil(Math.random()*8);
               
                //Set the initial speeds
                speedX[i] = generatedSpeedX;
                speedY[i] = generatedSpeedY;
               
                //Set the x and y directions of each particle
                (Math.ceil(Math.random()*2) === 1) ? directionX[i] = 1 : directionX[i] = -1;
                (Math.ceil(Math.random()*2) === 1) ? directionY[i] = 1 : directionY[i] = -1;
               
                //Each particle has a different radius
                radius[i] = 10 + Math.ceil(Math.random()*5);
            }
           
            //Fire our draw function every 10ms
            setInterval(this.draw, 20);
        },
        //Fun this function to draw a frame
        draw: function(){
            //Clear the last frame by filling it black (clearRect caused issues in chrome)
            c.fillStyle = "black";
            c.fillRect(0,0,width,height);
               
                //Loop through each particle
                for(i=10;i<width;i+=30){
                   
                    //Change its x direction if it gets to the boundry
                    if(posX[i] > width || posX[i] < 0){
                        directionX[i] *= -1;
                    }
                    //Change its y direction if it gets to the boundry
                    if(posY[i] > height || posY[i] < 0){
                        directionY[i] *= -1;
                    }
                   
                    //Save the canvas settings before we modify anything (clip)
                    c.save();//Before path
                   
                        //For each particle begin a path around its position
                        c.beginPath();
                        c.arc(posX[i],posY[i],radius[i],0,Math.PI * 2,false);
                        //Clip the path ready to be filled later
                        c.clip();
                       
                        //Set the red radial gradient alpha to 1
                        c.globalAlpha = 1;
                       
                        //Create our red radial gradient up the top left corner
                        var radGradRed = c.createRadialGradient(width/4,height/4,100,width/4,height/4,width/4);
                        radGradRed.addColorStop(0, 'red');
                        radGradRed.addColorStop(1, 'black');
                        c.fillStyle = radGradRed;
                       
                        //Create a rectangle filled using the red radial
                        c.fillRect(0,0,width,height);
                       
                        //Set the global alpha to 0.6 for the next box (so the red shines through)
                        c.globalAlpha = 0.6;
                       
                        //Create our green radial gradient up the top right corner
                        var radGradGreen = c.createRadialGradient(3*width/4,height/4,100,3*width/4,height/4,width/4);
                        radGradGreen.addColorStop(0, 'green');
                        radGradGreen.addColorStop(1, 'black');
                        c.fillStyle = radGradGreen;
                       
                        //Create a rectangle filled using the green radial
                        c.fillRect(0,0, width, height);
           
                        //Set the global alpha to 0.4 for the next box (red / green shines through)
                        c.globalAlpha = 0.4;
                       
                        //Create our blue radial gradient at the bottom middle
                        var radGradBlue = c.createRadialGradient(width/2,3*height/4,100,width/2,3*height/4,width/4);
                        radGradBlue.addColorStop(0, 'blue');
                        radGradBlue.addColorStop(1, 'black');
                        c.fillStyle = radGradBlue;
                       
                        //Create a rectangle filled using the green radial
                        c.fillRect(0,0,width,height);
                   
                    //Restore the canvas to how it was before the clipping path
                    c.restore();
                   
                    //Set the new x and y position of the selected particle
                    posX[i] += directionX[i]*speedX[i];
                    posY[i] += directionY[i]*speedY[i];
                }
           
            //Set the new global alpha to 0.9 for the static particles
            c.globalAlpha = 0.9;
           
            //Create a red particle top left
            c.fillStyle = "red";
            c.beginPath();
            c.arc(width/4,height/4, 20, 0, Math.PI * 2, false);
            c.fill();
           
            //Create a green particle top right
            c.fillStyle = "green";
            c.beginPath();
            c.arc(3*width/4, height/4, 20, 0, Math.PI * 2, false);
            c.fill();
           
            //Create a blue particle middle bottom
            c.fillStyle = "blue";
            c.beginPath();
            c.arc(width/2, 3*height/4, 20, 0, Math.PI * 2, false);
            c.fill();
        }
    };
}();

//Call the init function and get the particles moving
animate.init();

Or for those of you who are a fans of jsFiddle I’ve pasted it here for you look at.

JS1k: JavaScript Optimisations

Following up on my post on JS1k I thought I’d put together a small list of optimisations you can use if you’re looking to minify your code before compressing it. When you only have 1024 bytes to play with every byte counts!

Very obvious, store your objects for later use. You may need to use the document / context later so store them in a single byte variable.

1
2
3
4
5
6
7
8
//From:
var canvas = document.getElementById('c')
var context = canvas.getContext('2d')

//To:
d=document
t=d.getElementById('c')
c=t.getContext('2d')

If you are using Math object / functions, don’t repeat ‘Math’ over and over. It may not look much at first but it will pay off and save you loads of bytes

1
2
3
4
5
6
7
8
//From:
var colour = Math.floor(Math.random()*255);

//To:
M=Math
R=M.random
L=M.floor
colour = L(R()*255)

I found myself using 2 Pi and false quite a lot in the arc method, so why not minify them too.

1
2
3
4
5
6
7
//From:
context.arc(posX, posY, radius, 0, Math.PI*2, false);

//To (uses Math example above too):
P=M.PI*2
f=false
context.arc(posX, posY, radius, 0, P, f);

For your ‘draw’ function use an anonymous function within setInterval(). This will save you around 5 bytes.

1
2
3
4
5
6
7
8
9
10
//From:
function d(){
    //Code goes here...
}
setInterval(d,9)

//To:
setInterval(function(){
    //Code goes here...
},9)

When it comes to the canvas API it is necessary to minimise it’s usage at times since the methods can’t be stored like we did with the Math object. For that I used a mixture of arrays and for while loops.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//From:
ctx.fillStyle = "red";
ctx.beginPath();
ctx.arc(50, 100, 10, 0, Math.PI*2, false);
ctx.closePath();
ctx.fill();

ctx.fillStyle = "blue";
ctx.beginPath();
ctx.arc(143, 230, 15, 0, Math.PI*2, false);
ctx.closePath();
ctx.fill();//Add more if needed...

//To:
A=["red",50,100,10,"blue",143,230,15];
i=2;while(i--){
    D=i*4;
    ctx.fillStyle = A[D];
    ctx.beginPath();
    ctx.arc(A[D+1], A[D+2], A[D+3], 0, P, f);//We stored 2Pi and false in a var above
    //removed closepath() as this is done when fill is called
    ctx.fill();
}

Another quite obvious one, use the ternary operator instead of an if else statement.

1
2
3
4
5
6
7
8
9
10
//From:
if(Math.ceil(Math.random()*2) == 1){
    var result = 1
} else {
    result = -1
}

//To:
Math.ceil(Math.random()*2) == 1 ? result = 1:result = -1
//Remember you can minify the Math functions

An excellent couple of tips from Ben Almans blog post.

1
2
3
4
5
6
7
8
9
10
11
12
13
/*Backwards iterating while loop*/
//From:
for(i=0;i<20;i++){} //19 bytes

//To:
i=20;while(i--){} //17 bytes

/*Use 'this' instead of 'window'*/
//From:
w=window

//To:
w=this

Hopefully you’ve found some of these JavaScript short-cuts useful. Have any more to add to the list? Leave a comment below!

JS1k: Smaller is Better!

Every once in a while something really grabs my interest. Since being introduced to Mr doob and remembering back to the amazing demos you used to get on various Amiga software (cough..); the demoscene has got me thinking about all the cool new possibilities for Web Developers. You can easily use JavaScript and the canvas tag and go visualisation crazy. Now when you mix in another factor: set a limit to the size of the code you can use; then it becomes really interesting!

Peter van der Zee has created a competition called JS1k, where the aim is to see what you can achieve using JavaScript and a canvas tag with a strict limit of 1024 bytes (1k). To put that in perspective, your average web page is 507K, so there isn’t a lot of room for manoeuvre! Saying that though, you will be surprised with what you can achieve with only 1024 bytes. Take a look at some of the accepted submissions and prepare to be amazed.

I’ve submitted my own entry which you can view here, it hasn’t been updated to the latest version yet but I’m sure it will be soon.

Particles changing colour depending on their proximity to different coloured circles.

My entry into JS1k. Looks simple but was a challenge to come under the 1k limit

Compared to some of the entries my demo looks a tad boring, but I really enjoyed putting it together. Condensing a script that was originally over 3.5k down to 1024 bytes was a real challenge. The last 20 bytes in particular were a pain in the backside! In version 0.2 I hit 1024 bytes exactly as I hadn’t realised you also have to style the demo page slightly if you use a black background. I think I may be able to shave a couple more bytes off, but I’m really coming close to the limit. The resulting code isn’t pretty and in no way readable, but it works! Take a look for yourself with a “view source” on the demo page.

According to Peter the competition is open until the 10th September, so I may just create a new demo and see how I get on. On a side note, JavaScript wizard p01 has dabbled with even smaller code limits of 512, 256 and 128 bytes. The results of which you can see here. Impressive stuff! I’m looking forward to seeing his entry into the competition.

Update: After a few optimisations and a run through the closure compiler I managed to get the demo down to 957 bytes.

Investigating the HTML5 Drag and Drop API

One of the many new API’s available to developers with HTML5 is the Drag and Drop API. Drag and Drop is nothing new, it has been available for quite a few years now using JavaScript libraries (Prototype + script.aculo.us, Mootools, jQueryUI, YUI), and even in IE; HTML5 drag and drop is based on work done by Microsoft in 1999 (wow!). That being said, each library implements it in their own specific way, usually this isn’t a problem but it means all developers on a team have to know how that library works, which isn’t always the case. Luckily the HTML5 D&D API promises to level the playing field by supporting drag and drop natively in the browser using vanilla JavaScript (but how long it will take for this to happen is anybody’s guess).

It’s an interesting time for JavaScript libraries with these new native browser API’s coming out. I’m guessing it will be a case that if the browser supports drag and drop natively the method will be used, if not then fall back the the library implementation. For the developer this should mean no extra code, but for the library authors it could be a little tricky as browsers gradually roll out native support. Browser sniffing is never a good thing, but if browser vendors don’t agree on it’s implementation it may be required.

To see how HTML5 drag and drop works I’ve put together a simple little experiment. The experiment allows you to drag a thumbnail image onto a drop area to load a larger version of the image using the canvas element. It’s easy to see how this could be adapted for use in an e-commerce shopping cart or an online web application (perfect for iPad users!). Note: I’ve only tested in FF 3.6, I don’t want to get into the cross-browser issues (yet!), but for more information about what a mess it seems to be take a look at “The HTML5 drag and drop disaster” by Peter-Paul Koch.

It’s fairly simple to start using the drag and drop API, you just need to add a couple of attributes and events to an element to make it drag-able, then attach events to the drop element to do something with the data from the dropped element:

1
2
3
4
5
6
<!-- Our dragabble element -->
<li draggable="true" ondragstart="event.dataTransfer.setData('text/plain', 'bonfire'); event.dataTransfer.mozSetDataAt('application/x-moz-node', event.target, 0);">
    <img src="images/bonfire.jpg" alt="bonfire" id="bonfire">
</li>
<!-- Our drop area -->
<div id="drop" ondragover="dragDrop.imageDragOver(event);" ondrop="dragDrop.imageDrop(event);" ondragleave="dragDrop.imageLeave(event);"></div>

Now create a function that will fire once an element has been dropped; this will collect the data from the element and do something with it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
    //.....
        imageDrop: function(event){
            //Does the drop contain node data?
            var isNode = event.dataTransfer.types.contains("application/x-moz-node");
           
            var imageData;
            //Look for node data first, fall back to plain if unavailable
            if(isNode){
                var nodeData = event.dataTransfer.mozGetDataAt("application/x-moz-node", 0);
                imageData = nodeData.id;
            } else {
                imageData = event.dataTransfer.getData("text/plain");
            }
            //Pass the data to the image load function
            this.loadCanvasData(imageData);
           
            //Prevents page jumping to the image
            event.preventDefault();
        },
    //.....
}

As you can see from the example above, it is possible to pass different types of data to the drop element. These include plain text, links, HTML / XML, files, images and document nodes. I’ve used the document node data by default and have added a check to see if the dragged element has any node data attached to it, if not it falls back to plain text data. You can see this check in action by dragging image two over the drop area. The background will turn red signalling that there is no node data attached. If you wanted, you could restrict what data type accepted by the drop box, but I added a fall back instead.

The example only took a couple of hours to put together, it may be very simple but it demonstrates how easy it is to implement the drag and drop API into your web application (assuming all browsers agree on how it should be implemented!).

Using Image Data Inside the HTML5 Canvas Element

One of the interesting features of the HTML5 canvas API is it’s ability to manipulate pixel information at the individual byte level. Reading data and inserting it into the canvas is very simple:

1
2
3
4
5
6
7
8
9
10
11
12
//Select our canvas and context
var canvas = document.getElementById("imageCanvas");
var context = canvas.getContext('2d');
//Select the image from the DOM
var selectedImage = document.getElementById("image");

//Draw the image to the canvas then read in the data
context.drawImage(selectedImage, 0, 0);
var originalLakeImageData = context.getImageData(0,0, width, height);

//Image data is now stored in an array in the form
//originalLakeImageData.data = [r1, g1, b1, a1, r2, g2, b2, a2....]

The data is stored in one long array with 4 values for every 1 pixel in the image (red, green, blue, alpha). Once you have the data inside a variable you can start manipulating it in whatever way you like. I’ve put together 3 demos displaying some pixel manipulation in action, you can view them here.

Images manipulated using the canvas API

  • Slider: This demo simply shows a before and after shot of a photograph I have cleaned up in Photoshop. As you slide your mouse over the image it reads in data from each data set. I find this is quite a good way of displaying before and after shots.
  • Pointer: Uses the same image data as the previous demo, only this time a region is picked out of the new image data set and displayed instead of the old image data. This gives the effect of ‘looking through’ the old image to the new one underneath.
  • Black & White: This demo only uses 1 data set, depending on where you hover the mouse you get a black and white image. The image data is read for every pixel, an average of the colour is calculated and pushed back into the data set. This results in a black and white image.

Some inspiration for the experiment was taken from the extremely useful jQuery Before / after plug-in, which I’ve yet to use but do plan to in the future.

One issue you may notice is the fact that the swipe is slightly sluggish; this is because as you hover over the image, the JavaScript is looping through every pixel in the image continuously:

1
2
3
4
5
6
7
8
9
10
//Loop through all the pixels in the image
//Top to bottom
for(var j = 0; j < selectedImageData.height; j++){
    //Left to right
    for(var i = 0; i < selectedImageData.width; i++){
        //Look for our individual pixel info (Times 4 due to RGBA)
        var index = (j * 4) * selectedImageData.width + (i * 4);
        //Do something with the data...
    }
}

That works out to be a staggering 720,000 bits of information in the data array (300px x 460px x 4) per image. The smaller your images are the smoother the effect, so there is a limit to how effective this method can be. I won’t be using it on a live project any time soon, but the demos have allowed me to experiment with manipulating image data using canvas.

Note: You may be wondering where the images are from. They are both my own images, one is of my Great Uncle Ben, who along with my Grandfather and their older brother Joe, fought in the Second World War. Here you can see him in uniform in North Africa. The second image was taken by me at Ullswater Water in the Lake District in 2006.

Currently on page 1 of 3123