1. State Stack
5. Custom Transforms
6. An Example: Ellipses
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.
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.
To translate means to move the canvas. The syntax is
ctx.fillRect(5,5,20,20); ctx.translate(30,30); ctx.fillRect(5,5,20,20);
To rotate means, well, rotating the entire canvas around its origin. The syntax is
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);
To scale means making the canvas larger/smaller, using the origin as the centre point. The syntax is
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();
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)
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();