Course notes 20
syllabus | schedule | exercises | assignments | class notes | resources | students | ARTC courses

Canvas Transformations

By employing transformations, one can translate, rotate, and scale elements by altering the state of the canvas context. The methods described below allow objects to receive the effects of the ascribed transformations, but the objects themselves are not being manipulated. The context is the target of the transformation.

Visualize the grid that would exist on the canvas. As you draw and interact upon the canvas, your actions are based on the layout of this grid. If you were to change the grid, it would change how the objects you later draw would be perceived. This is how canvas transformations operate. A typical workflow consists of:

  1. Save the initial/current state of the context
  2. Apply some combination of transformations
  3. Draw with the newly transformed state
  4. Restore to the initial/previous state of the context

The state can be saved and restored by calling the appropriate context.save() and context.restore() functions. In addition to transformations, stroke styles, fill styles, path attributes, and shadow attributes will also be saved.


Translate

context.translate(x , y);

The translate function moves the origin of the context to the new coordinates. Any future positioning, while under the transformation, is then based off of this new origin.

In the example on the left, a rectangle is first drawn at the origin under the initial state. As a result, the rectangle appears in the upper left corner. Next, the context is translated to the center of the canvas, making the new origin equal to (150,150). When the second rectangle is draw, it is placed based on the newly translated coordinate plane. Thus the passed (0,0) becomes equivalent to what would have been (150,150) prior to the translation. To further illustrate the situation the same translation has been applied in the example on the right over the top of a grid. The red lines intersect at (0,0) prior to the transformation, and the blue lines intersect at (0,0) after the transformation.

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

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

ctx.translate(150,150);

ctx.fillRect(0,0,50,50);
var canvas= document.getElementById('canvas')
var ctx = canvas.getContext('2d');

ctx.strokeStyle = 'red';
ctx.lineWidth = 3;

ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(0,canvas.height);
ctx.stroke();
ctx.moveTo(0,0);
ctx.lineTo(canvas.width,0);
ctx.stroke();


ctx.translate(150,150);
ctx.strokeStyle = 'blue';

ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(0,canvas.height);
ctx.stroke();
ctx.moveTo(0,0);
ctx.lineTo(canvas.width,0);
ctx.stroke();

Rotate

context.rotate(radianAngle);

The rotate function makes a clockwise rotation of the context around the origin to the specified angle measurement. It is important to note that similar to the arc function, the angle value is measured in radians. See radians conversion list for more information.

In the first example, rectangles are used to demonstrate how the surface rotates. The first rectangle is drawn normally - from the origin with a width of 100 pixels and a height of 30 pixels. Then the context is rotated 45 degrees and the same rectangle is drawn again. The second example illustrates the same transformation with lines atop a grid; red being before the rotation, and blue after.

The pivot point of the rotation is the 0,0 coordinate of the context.

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

ctx.fillRect(0,0,100,30);

ctx.rotate(Math.PI/4);

ctx.fillRect(0,0,100,30);
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

ctx.strokeStyle = 'red';
ctx.lineWidth = 3

ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(canvas.width,0);
ctx.stroke();

ctx.rotate(Math.PI/4);

ctx.strokeStyle = 'blue';
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(canvas.width,0);
ctx.stroke();

Scale

context.scale(xFactor,yFactor);

The scale function allows for multiplicative size adjustment of the context's grid by the supplied x and y factors. The default scale of 1 unit = 1 pixel is noted by a factor of 1.0. Calling the scale function with values below 1.0 will reduce the size, and values greater than 1.0 will increase the size of elements drawn onto the canvas.

Note: Supplying negative values will create a mirroring effect of the effected values.

The first example demonstrates scaling with color rectangles. First, a red rectangle is drawn from the origin with a size of 200 pixels by 200 pixels. Then the context is scaled uniformly by a factor of 0.5, reducing future drawn shapes to 50% of their specified size. This is seen in the blue rectangle which is drawn next. Although its size values are still 200 pixels by 200 pixels, on the canvas it appears at 1/2 the size of the previously drawn red rectangle. The second example draws the same shapes and transformation instead stroking the rectangles on top of a grid. Notice how the stroke width is also scaled.

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

ctx.fillStyle = 'red';
ctx.lineWidth = 3
ctx.fillRect(0,0,200,200);

ctx.scale(0.5,0.5);

ctx.fillStyle = 'blue';
ctx.fillRect(0,0,200,200);
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

ctx.strokeStyle = 'red';
ctx.lineWidth = 3
ctx.strokeRect(0,0,200,200);

ctx.scale(0.5,0.5);

ctx.strokeStyle = 'blue';
ctx.strokeRect(0,0,200,200);

Bringing It All Together

Most useful implementations of transformations will involves a combination of translate, rotate, and scale. As more complex transformations are constructed, it is important to keep track of the context's state. This is where making calls to context.save() and context.restore() will be helpful. This final example combines translation, rotation, and scaling to create a basic spirograph-like illustration. Notice how the context's state is saved and restored in order to return to the initial condition. Otherwise, individual transformations would begin to compound, resulting in undesirable results.

var canvas = document.getElementById('ex04');
var ctx = canvas.getContext('2d');
var width = canvas.width;
var height = canvas.height;

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

for(var i=1; i <= 4;i++){
    ctx.save();
    
    var factor = 1 * (1/i)
    ctx.scale(factor,factor);
    var angle = 0;
    
    while(angle < 360) {
        ctx.strokeStyle = 'rgb('+Math.floor(Math.random()*(255+1))+',0,150)';
        ctx.rotate(angle*(Math.PI/180));
        ctx.beginPath()
        ctx.arc(80,0,50,0,Math.PI*2,false);
        ctx.closePath();
        ctx.stroke();
        angle += 2;
    }
    
    ctx.restore();
}