(click anywhere to close)
OPEN MENU

[Canvas] Transformations

category: Website | course: Canvas | difficulty:

Transformations, in CSS at least, are what you’d apply to a complete element to move, rotate or scale it. Transformations work that way in nearly all languages – but not canvas. You can’t transform individual elements, but only the complete canvas. This allows you to modify multiple shapes (in the same way) at once, and add lots of transforms on top of each other. The obvious question here is: what if we want to reset all those transforms we’ve chained together, or just want to modify only a single shape at a time? For this, there is the state stack.

State Stack

The state stack is simply a list of states you saved, in a certain order, that canvas keeps track of.

To save the current state of the canvas, which means the current set of transforms applied, use save(). This adds the current state on top of the state stack.

To restore the last state you saved, use restore(). This reads the state on top of the state stack, and resets the canvas to that state. Once that’s done, this state is removed from the state stack.

//Save inital state of canvas
ctx.save();

//Rotate the canvas, created filled rectangle
ctx.rotate(0.2*Math.PI);
ctx.fillRect(0,0,30,30);

//Restore the initial canvas state, without rotation
ctx.restore();
ctx.fillRect(35,35,20,20);

The reason I wanted to start with this is because it becomes very important once you start using multiple transforms. Now let’s look at all the available transforms!

Even though the focus is on transforms here, the state of a canvas also saves all the currently set properties, such as fillStyle or strokeStyle. So, if you’re working with canvasses with lots of objects, you’ll still need to make heavy use of the state stack, even if you don’t use transform operations.

Translate

To translate means to move the canvas. The syntax is

translate(xOffset, yOffset)

ctx.fillRect(5,5,20,20);
ctx.translate(30,30);
ctx.fillRect(5,5,20,20);

Rotate

To rotate means, well, rotating the entire canvas around its origin. The syntax is

rotate(radians)

Note that the angle must be set in radians, not degrees.

//Note that it uses the upper left corner as centre of rotation
ctx.rotate(0.1*Math.PI);
ctx.fillRect(50,50,30,30);

Scale

To scale means making the canvas larger/smaller, using the origin as the centre point. The syntax is

scale(xScale, yScale)

The default scale is 1, which means a value smaller than that makes everything smaller, and a value larger makes everything larger. Negative values are also allowed, and mirror/flip the canvas.

//Scale canvas 150% both directions, flip everything vertically
ctx.scale(1.5,-1.5);

//Draw rectangle pointing upwards. 
//Note that we draw it above the canvas, as the scaling transformation uses the top of the canvas as its origin.
ctx.beginPath();
ctx.moveTo(20,-40);
ctx.lineTo(40,-80);
ctx.lineTo(60,-40);
ctx.closePath();

ctx.stroke();

Custom Transforms

With custom transforms you can easily perform multiple transform operations at the same time. That’s why the method takes no less than six parameters:

transform(scaleX, skewX, skewY, scaleY, translateX, translateY)

As you can see, rotation isn’t easily achieved with this method. Another tiny problem is that things can behave unexpectedly, because you’re applying so many transforms at the same time, on top of all the existing transforms.

To solve this, one extra method exists that removes all current transforms, before applying the new one you supply:

setTransform(scaleX, skewX, skewY, scaleY, translateX, translateY)

ctx.transform(1,5,5,1,30,30);

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

An Example: Ellipses

One very basic shape we haven’t seen yet are ellipses. Well, now we can make them with the help of some nifty transformations!

//Save state
ctx.save();

//Translate ctx
ctx.translate(20,20);

//Scale ctx horizontally
ctx.scale(2, 1);

//Draw circle which will be stretched into an oval
ctx.beginPath();
ctx.arc(20,20,10, 0, 2 * Math.PI, false);

//Restore to original state
ctx.restore();

//Apply styling
ctx.fillStyle = '#8ED6FF';
ctx.fill();
ctx.lineWidth = 5;
ctx.strokeStyle = 'green';
ctx.stroke();
CONTINUE WITH THIS COURSE
Do you like my tutorials?
To keep this site running, donate some motivational food!
Crisps
(€2.00)
Chocolate Milk
(€3.50)
Pizza
(€5.00)