agillo.net | about me

Circle Trails

Tested in chrome/safari. Not as smooth as desired in firefox

Since I've read Secrets of the javascript Ninja my opinion of javascript has risen further. Initially a lot of its peculiarities seem slightly evil, and when coming from a language like java, downright counterintuitive. I would use this and hope for the best, or ponder the sobriety of the developer who came up with something like:

(function() {})();

Yet, it turns out these things aren't that hard and actually make a lot of sense. A whole playground of patterns and techniques, not typically found in more conventional languages, is yours for the taking!

Now add to that HTML5 goodness, like canvas, and you can do some really cool things with almost no code. Take the example above made up of ~40 lines of code.

function circleTrails(canvas) {
    var ctx = canvas.getContext("2d");
    var circles = {}

    function Circle(x,y) {
        var id = Math.random();
        circles[id] = this;
        var i = 0;

        this.draw = function() {
            ctx.beginPath();
            ctx.arc(x, y, i+2,0 , 2*Math.PI, false);
            var color = i*3;
            ctx.strokeStyle = 'rgb('+color+','+color+','+color+')';
            ctx.stroke();
            i++;

            if (color > 256) {
                delete circles[id];
            }
        }
    }

    canvas.addEventListener("mousemove", function(e) {
        new Circle(e.offsetX, e.offsetY);
    })

    var timerId = setInterval(function() {
        ctx.clearRect( 0 , 0 , canvas.width , canvas.height );
        for (var key in circles) {
            circles[key].draw();
        }
    }, 25);
};

circleTrails(document.getElementById('canvasId'));

Breaking it down

An Outer Function

function circleTrails(canvas) { ... }

circleTrails(document.getElementById('canvasId'));

first we create an outer function which we will pass our canvas object to, we can grab the canvas with

document.getElementById(canvasId)

Why wrap everything in a function if we are just going to call it once? We do this to contain the variables, and not pollute the global scope; not really necessary here, but just good practice.

Additionally if we had multiple canvases we could add the circle trail behaviour to all of them without the worry of them interfering with one and other.

The Circles

var circles = {}

function Circle(x,y) {
    var id = Math.random();
    circles[id] = this;
    var i = 0;

}

First, we create an object which we will use as a map that holds all our circle objects:
var circles = {}

The function Circle(x,y) is then a constructor for our actual Circle objects. We will create one of these objects each time we move the mouse with an x, y coordinate for the current position of the mouse. The responsability for managing and drawing the growing circle fading out on that position is then entirely placed on the Circle.

The first thing a Circle object does is create a uid for itself, which we crudely do with the help of Math.random(). Next it adds itself to the circles map with the id as it's key. It also initializes a variable i = 0 which will track how many times it has been drawn so far.

Drawing it

 this.draw = function() {
    ctx.beginPath();
    ctx.arc(x, y, i+2,0 , 2*Math.PI, false);
    var color = i*3;
    ctx.strokeStyle = 'rgb('+color+','+color+','+color+')';
    ctx.stroke();
    i++;

    if (color > 256) {
        delete circles[id];
    }
}

We draw a circle using the canvas 2d context by first indicating we want to draw something by hand with ctx.beginPath().

Next we draw the actual circle ctx.arc(x, y, i+2,0 , 2 * Math.PI, false);

Apart from growing in size each time we redraw it, we also want it to fade out. rgb(0,0,0) represents black, and rgb(256,256,256) represents white. We move within this spectrum getting lighter in steps of 3.

Finally we add a check, when the color > 256 the circle is now white, and technically invisible against the white background. It has completely faded out, so we completely remove it from the active circles map.

Tick : Managing the circles

var timerId = setInterval(function() {
    ctx.clearRect( 0 , 0 , canvas.width , canvas.height );
    for (var key in circles) {
        circles[key].draw();
    }
}, 25);

We create an interval that will update every 25 milliseconds. It first erases the whole board with ctx.clearRect( 0 , 0 , canvas.width , canvas.height );.

Next it moves through our map of active circles, telling each one to draw itself. Recall that circles will remove themselves from this map once they have drawn themselved into redundancy (completely white).

The event

canvas.addEventListener("mousemove", function(e) {
    new Circle(e.offsetX, e.offsetY);
})

The last step is to add an event listener on the canvas, so that every time we move our mouse a new Circle is created.

blog comments powered by Disqus