Canvas – Animation

Outside of the canvas api but part of HTML5, the widely supported requestAnimationFrame method replaces the old setInterval() and setTimeout() functions of the past for browser animation. A basic setup is as follows:

var start, elapsed, fps;

function animate(timestamp) {
    if (!start) start = timestamp; // first time
    elapsed = timestamp - start;
    start = timestamp;             // reset baseline
    fps = 1000 / elapsed;
    window.requestAnimationFrame(animate);
}

window.requestAnimationFrame(animate);

The animate function fires at whatever the browser determines is an optimal framerate, generally 60 times a second. If a browser tab running animate code is not visible, it may not be called at all (chrome), or may be called once a second (firefox); the actual behavior of browsers in this situation is unspecified. A timestamp is passed as an argument to the callback routine set to a value from Performance.now()  at the time the callback was triggered.

With this timestamp value,  we can calculate effective frames per second and make adjustments to ensure animations run at a constant rate. Or we can use elapsed time to perform actions once a second or in intervals other than the framerate.

To move at a certain number of pixels per second, regardless of the framerate:

ball.vx * (elapsedTime / 1000);
ball.vy * (elapsedTime / 1000);

Where ball.vx and ball.vy = the number of pixels per second.

To finesse the timing of a sweeping second-hand, we can calculate the number of radians traversed per second by chopping a circle up into 60 segments: 2PI/60 and then increment the angle based on how much of the second has elapsed:

angle += 2PI/60 * (elapsed/1000);

To start the second-hand at a specific angle, like current seconds, it can be initialized like so:

angle = (Math.PI*2) * (new Date().getSeconds()/60) - Math.PI/2;

This adjusts for the fact that radian = 0 is actually at the 3 o’clock position of a circle, or 90 degrees.

Instead of hardcoding 60, any divisor from 0..n could be used to map the value n to its corresponding location on the circle.

The following experiment animates the second-hand as a line from the center to its time corrected position along the circumference each frame. This creates a smooth, sweeping movement. Another function plots the location of the current hour, displaying it as a translucent circle and displays the minutes numerically inside.

(Using text with canvas wasn’t covered yet, but is explained well here. Note also that two canvas elements are used and stacked one upon another using css. This allows the dial to be drawn once to improve performance, since it doesn’t change, and leverages the default transparency of a canvas.

See the Pen Clock by Kentskyo (@kentskyo) on CodePen.0

Canvas – Drawing Bamboo with Code

After drawing rectangles, lines, circles (arcs) and triangles, about all that’s left in the toy box is the curve. Canvas has two types of curves: quadratic and cubic; they differ only in the amount of control points used for bending the line.

Quadratic curves have two anchor points and one control point and curves in only one direction. The more complex cubic curve adds another control point and can curve in two directions.

Curves can be interwoven when drawing paths with lines and arcs, and they pick up wherever the current context point is (or from a explicit moveTo which established a new context point):

ctx.quadraticCurveTo(cpx, cpy, x, y);

where cpx and cpy is the control point and x,y is the endpoint

Playing with this:

// w = canvas.width
// h = canvas.height
ctx.clearRect(0,0,w,h);

ctx.save();
ctx.translate(w/2+15, h/2);
ctx.beginPath();
ctx.moveTo(0,0);
ctx.quadraticCurveTo(50, -25, 100, 0);
ctx.quadraticCurveTo(37, 36, 0, 0);
ctx.fill();

//ctx.setTransform(1,0,0,1,0,0); //reset matrix 
ctx.translate(w/2-15, h/2);
ctx.scale(-1, 1); // mirror image...
ctx.beginPath();
ctx.moveTo(0,0);
ctx.quadraticCurveTo(50, -25, 100, 0);
ctx.quadraticCurveTo(37, 36, 0, 0);
ctx.fill();

ctx.restore();

The code draws two curves connected to each other (then endpoint on the second was the starting point on the first curve); the second set of curves calls scale(-1,1) before repeating the same calls again and creates a mirror image, which results:

eyes1

 

 

Using curves for eyes and leaves in the next project here’s an experiment constructing a bamboo grove. Each time the canvas is clicked it regenerates a new grove.

See the Pen Haunted Bamboo by Kentskyo (@kentskyo) on CodePen.0

 

 

 

 

Canvas – Triangles

There’s no built in function in canvas for making triangles, but since a triangle is just a particular arrangement of 3 lines it should be easy enough to construct with lineTo:

ctx.beginPath();
ctx.moveTo(150,100);
ctx.lineTo(100,200);
ctx.lineTo(200,200);
ctx.fill();

Triangle Coordinates

When using fill(), open paths are closed automatically, from the current point back to the starting point. That’s why lineTo(150,100) wasn’t necessary at the end. If stroke() is used instead of fill, however, then a call to closePath() is required.

Rather than painstakingly arranging triangles at specific coordinates on the grid, a different approach is to move coordinates via translate and draw the triangle at 0,0. Using this approach to arrange a set of 10 triangles around the center of the canvas:

ctx.clearRect(0,0, w, h);
ctx.fillStyle = "red";

// Triangle points
var t = []; 
t[0] = {x:0, y:-5};
t[1] = {x:-5, y:5};
t[2] = {x:5, y:5};

for (var deg = 0; deg < 360; deg += 36) {
    var a = radians(deg);
    var x = Math.cos(a) * 25;
    var y = Math.sin(a) * 25;
    ctx.translate(canvas.width/2+x, canvas.height/2+y);
    drawTriangle(t);
    ctx.setTransform(1,0,0,1,0,0); //reset identity matrix
} 

function drawTriangle(t) {
   ctx.beginPath();
   ctx.moveTo(t[0].x, t[0].y);
   ctx.lineTo(t[1].x, t[1].y);
   ctx.lineTo(t[2].x, t[2].y);
   ctx.fill();
}

function radians(deg) {
    return deg * Math.PI/180;
}

trianglerad

Now playing with the orientation of the triangles by adding the line:

ctx.rotate(radians(deg+90));

just before the call to drawTriangle.

Adding a scale function is a lazy way to draw multiple rings

var p = [];
p[0] = {x:0, y:-5};
p[1] = {x:-5, y:5};
p[2] = {x:5, y:5};

ctx.clearRect(0,0, w, h);
ctx.fillStyle = "red";

for (var i=0; i<4; i++) {
    drawRing(p, 25, 36, 90, i);
}


function drawRing(p, r, stepBy, rDegrees, scale) {
    r = r*scale+(scale * 15); 
    for (var deg = 0; deg < 360; deg += stepBy) {
        var a = radians(deg);
        var x = Math.cos(a) * r;
        var y = Math.sin(a) * r;
        ctx.translate(w/2+x, h/2+y);
        ctx.rotate(radians(deg+rDegrees));
        ctx.scale(scale, scale);
        drawTriangle(p);
        ctx.setTransform(1,0,0,1,0,0); //reset identity matrix
    } 
}

arrows

Combining this exploration into a project:

See the Pen Triangle Spirals by Kentskyo (@kentskyo) on CodePen.0

 

Canvas – Lines, Circles and Spirals

It’s time to branch beyond rectangles for our drawing elements. To draw a line between two points with the canvas api, using the context object:

ctx.strokeStyle = "red";
ctx.lineWidth = 5;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(100, 100);
ctx.stroke();

beginPath starts recording a path. Like Illustrator and some other drawing applications a path is just a set of points it doesn’t become an actual line or shape until a stroke or fill is applied. The canvas can have only one path active at a time.

lineTo(x,y) draws a path from wherever the last point was positioned on the current path to the point directed by x,y in lineTo. This initial point can be set by moveTo(x,y) or it can be wherever lineTo left off in the last call. For example:

ctx.beginPath();
ctx.moveTo(canvas.width/2, canvas.height/2);
var color = randomInt(0,360);
ctx.strokeStyle = "hsl("+color+",60%,60%";
for (var i = 0; i < 10; i++) {
    ctx.lineTo(randomInt(50, canvas.width-50), randomInt(50, canvas.height-50));
}
ctx.stroke();

We had to start the path with a call to moveTo(x,y) but after that we just piggybacked on wherever lineTo(x,y) left the last end point to begin the next line.
randomlines

Circles

Rectangle is actually the only shape canvas knows how to draw. Everything else must be built from paths. A circle is created using the arc function and telling it the starting angle (in radians) is 0 and the ending is 2*PI:

arc(x,y, radius, start-angle, end-angle, anticlockwise);

ctx.beginPath();
ctx.arc(canvas.width/2, canvas.height/2, 100, 0, 2 * Math.PI, false);
ctx.fillStyle = 'red';
ctx.fill();
ctx.lineWidth = 20;
ctx.strokeStyle = '#400d12';
ctx.stroke()

2015-11-05_105722

Combining what we’ve covered so far:

function draw() {
    ctx.strokeStyle = "red";
    ctx.lineWidth = 1;

    drawCircle(canvas.width/2, canvas.height/2, 50);

    ctx.beginPath();
    ctx.moveTo(canvas.width/2, canvas.height/2);
    for (var i = 0; i < 10; i++) {
        var x = randomInt(50, canvas.width-50);
        var y = randomInt(50, canvas.height-50); 
        ctx.lineTo(x, y);
        ctx.stroke();
        drawCircle(x,y, randomInt(5,15));
    }
}


function drawCircle(x,y,r) {
    var c = randomInt(0,360);
    ctx.beginPath();
    ctx.arc(x, y, r, 0, 2 * Math.PI, false);
    ctx.fillStyle = "hsl("+c+",60%,60%";
    ctx.fill();
}

circlesandlines

Spirals

To draw a spiral we just need to draw a circle with a radius that either grows or shrinks.

var radius = 1.0;
ctx.translate(canvas.width/2, canvas.height/2);
for (var deg = 0; deg < 360*6; deg += 3) {
    var angle = deg * Math.PI/180;
    var x = Math.cos(angle) * radius;
    var y = Math.sin(angle) * radius;
    drawCircle(x,y,2);
    radius = radius + 0.2;
}

Switching the formula in the line above from:

var angle = deg * Math.PI/180;

to:

var angle = deg * 180/Math.PI;

creates this:
spiral11

Textures

Besides colors, a fillStyle for a stroke or solid fill can also be a pattern or a gradient. Patterns are loaded from images and have an option for how they are tiled across a fill via the repeat parameter:

// example from MDN
var img = new Image();
img.src = 'pattern.png';
img.onload = function() {
    var pattern = ctx.createPattern(img, 'repeat');
    ctx.fillStyle = pattern;
    ctx.fillRect(0,0,400,400);
};

Combining patterns with spirals was the focus of the next experiment

See the Pen Textured Spirals by Kentskyo (@kentskyo) on CodePen.0

 

 

Canvas – Transforms

The Canvas API has 3 basic transform functions. What’s a little non-intuitive about these functions is they act on the canvas itself, and only indirectly on the canvas contents.

In Canvas – The Beginning the coordinate system was introduced with 0,0 defaulting to the top left corner of the canvas; which was kind of weird because the 2D plots most are first introduced to depict 0,0 in the center of the graph.

Translate

The Translate function moves this 0,0 origin to an offset passed in the x,y argument. So to move the origin to the center of the canvas on the screen:

ctx.translate(canvas.width/2, canvas.height/2);

Transform functions effect what is drawn after the function, it doesn’t effect the contents that already on canvas. A rectangle drawn after the translate function call above:

ctx.fillRect(0,0,25,25);

Results in this

translate1

Rather than this (if drawn before the translate):

Note that the rectangle is drawn from its top left corner. So to center the rectangle in the canvas after the translate, its x and y need to be adjusted:

 var size = 50;
ctx.fillRect(-0.5*size,-0.5*size,size,size);

 

translate2

Rotate

Rotates a canvas around its current origin. That origin part is key to some nifty effects (and confusion sometimes.)

To rotate the rectangle drawn previously 45 degrees:

ctx.translate(canvas.width/2, canvas.height/2);
ctx.rotate(45*180/Math.PI);
var size = 50;
ctx.fillRect(-0.5*size,-0.5*size,size,size);

rotate

the rotate function takes radians as its argument and the formulas for switching between degrees and radians are:

radians = degrees * Math.PI / 180
degrees = radians * 180 / Math.PI

Note this will rotate everything that is drawn after rotate is called until the transformations are reset back to default either through:

ctx.save() and ctx.restore() which saves and restores the state of the canvas, including the state of the transformations, fills, line sizes, etc (but not the graphics on the canvas.)

or

ctx.setTransform(1,0,0,1,0,0);

which is faster, but just restores the state of the transformations back to their default settings.

Scale

ctx.scale(x,y) scales pixels (by default) x horizontally and y vertically where x and y are real numbers and 1.0 = current size and < 1.0 shrinks and > 1.0 expands. So 0.5 pixels would be half size and 2.0 would be twice as large.

Negative values can create some interesting effects:

// mirror horizontally
ctx.scale(-1, 1);

Trig and the Circle

Trigonometry is all about the triangle. What makes it useful for navigating points and angles around a circle is illustrated in this diagram.

trig

Trig functions, like sine and cosine are ratios of different sides of triangles at different angles (in radians) and this ratio can be used to draw an object at a specific point on the perimeter of a circle of any radius.

Another trig function, the tangent, or rather an inverse variant of it Math.arctan2(), when given the length of two sides returns the angle. Very handy for rotating one thing to face another or reading a value off interface elements like dials.

Using a couple of these transforms, and some trigonometry, the next experiment explores aesthetic effects of rotation on a simple construct. Try playing with the rotation speed and blur sliders to see a range of visuals that can be created from a few spokes of rectangles.

See the Pen Pinwheels by Kentskyo (@kentskyo) on CodePen.0