function drawNumbers(){
var rad, num;
cx.font= "30px Arial";
cx.textAlign = "center";
cx.textBaseline = "middle";
//numbers around the inner circumference
for(num=1; num < 13; num++){
rad = num * Math.PI/6; //angle for every number
cx.rotate(rad);
cx.translate(0, -175);
cx.rotate(-rad);
cx.fillText(num.toString(),0,0);
cx.rotate(rad);
cx.translate(0, 175);
cx.rotate(-rad);
}
}
function drawHands(){
//getting the time
var time = new Date();
var hours = time.getHours();
var minutes = time.getMinutes();
var seconds = time.getSeconds();
//setting the radians based on the time
//hour hand
hours %= 12;
hours = (hours * Math.PI/6) + (minutes * Math.PI/360) + (seconds * Math.PI/21600);
hands(hours, radius * 0.04, radius * 0.5);
//minute hand
minutes = (minutes * Math.PI/30) + (seconds * Math.PI/1800);
hands(minutes, radius * 0.03, radius * 0.65);
//second hand
seconds = (seconds * Math.PI/30);
hands(seconds, radius * 0.01, radius * 0.68);
}
function hands(ang, width, length){
cx.beginPath();
cx.lineWidth = width;
cx.lineJoin = "round";
cx.lineCap = "round";
cx.moveTo(0, 0);
cx.rotate(ang);
cx.lineTo(0, -length);
cx.stroke();
cx.rotate(-ang);
}
I was learning the HTML5 canvas in W3Schools and the tutorial was teaching how to make a working clock. 1. I just don't understand how the extra rotates work in the functions. 2. When applying a rotate function, does it always rotate from the center of origin (0, 0) of the canvas?
When you call the rotate
function it rotates the entire canvas, imagine holding a painting and then tilting it. It happens around the origin always. The way to rotate around a different point is to translate
the entire canvas first, then rotate
it.
Going back to the painting analogy, if we have rotated our painting, once we have drawn our line, we need to then restore the painting to being upright. Thus we rotate(-ang)
. If we had translated we would also have to undo our transation in a similar manner.
In the code below you can see I'm drawing a base black rectangle, and then calling a function which rotates the canvas by 0.5 radians and draws another rectangle twice. I haven't undone my rotation so the 3rd rectangle is actually rotated at 1 radian.
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
function drawRotatedRectangle() {
ctx.rotate(0.5);
ctx.fillRect(0, 0, 60, 50);
}
// Draw base rectangle
ctx.fillRect(0, 0, 60, 50);
// Rotate and draw second rectangle
ctx.fillStyle = "#FF0000";
drawRotatedRectangle();
// rotate and draw third rectangle
ctx.fillStyle = "#00FF00";
drawRotatedRectangle();
<canvas id="canvas"></canvas>
To fix this we modify the drawRotatedRectangle()
function to undo all translations and rotations that it made:
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
function drawRotatedRectangle() {
ctx.rotate(0.5);
ctx.fillRect(0, 0, 60, 50);
ctx.rotate(-0.5);
}
// Draw base rectangle
ctx.fillRect(0, 0, 60, 50);
// Rotate and draw second rectangle
ctx.fillStyle = "#FF0000";
drawRotatedRectangle();
// rotate and draw third rectangle
ctx.fillStyle = "#00FF00";
drawRotatedRectangle();
<canvas id="canvas"></canvas>
Now we see the red (hidden) rectangle and the green rectangle are at the same angle.
To demonstrate how we can rotate around a different location to the origin, we can first translate where our context origin is and then rotate our canvas. Below I move the origin to the center of the base rectangle, rotate the canvas and draw a rotated rectangle ontop of the base rectangle. Again the translations and rotations are restored in order of most recently applied.
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
function drawRotatedRectangle() {
// Move origin to center of rectangle
ctx.translate(30, 25);
// Rotate 0.5 radians
ctx.rotate(0.5);
// Draw rectangle where the center of the rectangle is the origin
ctx.fillRect(-30, -25, 60, 50);
// Undo our rotate
ctx.rotate(-0.5);
// Undo our translate
ctx.translate(-30, -25);
}
// Draw base rectangle
ctx.fillRect(0, 0, 60, 50);
// Rotate and draw second rectangle
ctx.fillStyle = "#FF0000";
drawRotatedRectangle();
// rotate and draw third rectangle
ctx.fillStyle = "#00FF00";
drawRotatedRectangle();
<canvas id="canvas"></canvas>
As Kaiido mentioned in the comments, the rotate(a)
function will round the input it is given so simply doing the reverse rotate(-a)
function will not return you to the original transformation.
The solution they suggested is to set the transformation matrix to the desired location with setTransform()
, in these example we are only returning to the original transform of the canvas so we can use the identity matrix:
ctx.setTransform(1,0,0,1,0,0)
Alternitively, you can also use save()
and restore()
methods. These will act like pushing the current state of the canvas context to a stack and when you restore()
it will pop the latest state from the stack returning you to the previous transform. This article by Jakob Jenkov explains this method further with some examples.