Transforming objects in your HTML5 Game or Experiment requires a bit of understanding regarding how they work. The behaviour and end result is not as intuitive as CSS3 transform functions like rotate()
or scale()
. I could quickly show you how to rotate or scale an image with its center as the origin, but it’s going to be super useful to take an indepth look at how the entire system works.
Canvas Coordinate System
You probably already know about the Cartesian coordinate system used by the 2D Rendering Context of HTML5 Canvas. Top left is the origin point with the coordinates (0,0). As you move to the right, the values increases on the X axis. Although, moving downwards increases values on the Y axis, unlike the standard cartesian plane where the values would decrease on the negative side of the number system.
What's the one thing every developer wants? More screens! Enhance your coding experience with an external monitor to increase screen real estate.
Translating Objects
Before getting into rotating and scaling, let’s first understand the concept of translating. Translating basically specifies the (x,y) position at which you want to render something.
The translate
method on the 2D context allows you to move the origin point of the context. Not making sense ? Basically with this method you can specify a new (x,y) coordinates on the context that’ll act as (0,0) after calling the translate
method.
Let me show you some code to make things uber clear.
var canvas = document.querySelector('canvas'); var ctx = canvas.getContext('2d'); ctx.fillStyle = 'red'; ctx.fillRect(0, 0, 100, 100);
We make a call to fillRect
. Where is the rectangle going to render ? At (0,0) with a width of 100px and height of 100px. Yeh, you got that right!
Now, let’s add a bit more code that uses the translate
method.
var canvas = document.querySelector('canvas'); var ctx = canvas.getContext('2d'); ctx.fillStyle = 'red'; ctx.fillRect(0, 0, 100, 100); // We "move" the origin of our coordinate system // to (100,100) of the current 2D Rendering Context ctx.translate(100, 100); ctx.fillStyle = 'blue'; ctx.fillRect(0, 0, 100, 100);
Our second fillRect
call is exactly same as the first one, so it should render a blue square atop the red square, right ? NO! It will not. Using the translate
method we shifted (0,0) – origin point – of the entire 2D rendering context to (100,100).
Demo time!
Gotcha!
Rotating Objects
Finally, after hours of reading, we’re onto the key part. Rotating Objects in Canvas. I am not going to deal with simple rectangles now. Instead we’ll rotate images drawn using the drawImage
method on the canvas context. Why ? Because that’s what you’re going to use in 99% cases (games, etc.). You can always replace the drawImage
called with fillRect
or something else anytime you want.
Basically this is the code we use (refresh if you don’t see the image).
var img = new Image(); img.src = 'http://cssdeck.com/uploads/media/4/4M29duK.png'; ctx.drawImage(img, 100, 100);
First argument to drawImage
is an image object whose src is set to the Image’s source. Second and Third arguments are the start X and Y positions to draw the image on the canvas. You can pass in some extra optional arguments, but life is just fine with what we have at the moment.
So, I want to rotate the image by 30 degrees. Let’s do it then.
var TO_RADIANS = Math.PI/180; var img = new Image(); img.src = 'http://cssdeck.com/uploads/media/4/4M29duK.png'; ctx.rotate(30 * TO_RADIANS); ctx.drawImage(img, 100, 100);
Take a look at the demo now:
So, we had our rotation done. The image rotated but a part of it is cut and its positions have changed. This might not exactly be what you or me wanted. If we had a div with that background image and rotated that with CSS3 transforms, then we might have gotten something according to our desire. Basically, the rotation did not occur around the center of the original position of the image (like a clock’s hand).
But Why?
That’s a smart question. The reason is, the rotate
method rotates the entire 2D Rendering Context, and NOT just the following object/figure that is drawn. See the following figure:
Clear now ? Great!
Solving the Problem
Let’s work towards the solution now. We just learnt about translate
sometime ago. Using translation with rotation will give us something like this:
Let’s use this knowledge by translating our rendering context (before rotating).
ctx.translate(100,100); ctx.rotate(30 * TO_RADIANS); ctx.drawImage(img, 0, 0);
Notice carefully. The second and third argument values to drawImage
have been used as the first and second arguments to translate and they’ve been set to 0 themselves. This little trick can save time!
So first, we translated our rendering context to (100,100)
– the old drawImage's
start x/y positions. Next we rotate the context and finally we draw our image. But, this is still not what we want. We want to rotate the image exactly at it’s center. Well, that’s quite easy.
We just need to translate a bit more by about half the width (X axis) and height (Y axis) and then only rotate. Then we need to draw by that same amount (half of width and height) as negative start (x,y) offsets (to drawImage
). Negating is very important since we are doing that extra translation.
ctx.save(); ctx.translate(100,100); ctx.translate(img.width/2, img.height/2); ctx.rotate(30 * TO_RADIANS); ctx.drawImage(img, -img.width/2, -img.height/2); ctx.restore();
Following is what we wanted, right ?
We made it! Notice those 2 extra method calls ctx.save()
and ctx.restore()
? Using methods like translate
and rotate
, we changed our coordinate system. This means any further drawing after drawImage
is going to occur on the altered coordinate system. This is not what we want. We need to reset back to our good old healthy system instead of this screwed version.
We do that by first saving the system using ctx.save()
-> apply all transformations -> restore to our good old system by calling ctx.restore()
. So cool!
Scaling Objects
The scale
method scales (or resizes) the 2D Rendering Context (and not just the following drawings). See this code.
ctx.save(); ctx.scale(2,3); ctx.drawImage(img, 100, 100); ctx.restore();
We just multiplied the size of X axis by 2 and also made the Y dimension 3 times larger – ctx.scale(2,3)
. Due to this, the 100 that we pass as start_x
to drawImage
is now 2x the size, i.e, equal to 200. Similarly, 100 that we pass as start_y
is 3x the size, i.e., equal to 300! So, not only did all our drawing’s scale up, but also the coordinates scaled. This is an important thing to remember.
Now the thing that we’ll want to do often is, scale our object (or image) in such a way, that it’s center (origin) is same as the one of the non-scaled (original) version. The trick to use here, is same as the one we used for rotation.
ctx.save(); ctx.translate(100,100); var half_width = img.width/2; var half_height = img.height/2; ctx.translate(half_width, half_height); ctx.scale(2,3); ctx.drawImage(img, -half_width, -half_height); ctx.restore();
And here’s the last demo:
Conclusion
Basically, now we know how to “control” the origin point for our Canvas transformations. Hope this is gonna help you out in several cases. Let’s have further discussions in the comments.
Great article! Very nicely explained.
really nice tutorial..that’s what i want exactly ..thanks
Great article, It helped me fix my collision detection issue on rotating shapes.
Thanks for taking the time to write this!
Very instructive article. But I start finding Canvas too complicated! There should be a method for rotating things more easily. The fact that one needs to read more than one article to understand a single rotation method is too much!
You think this kind of canvas is complicated? You should see the canvas WebGL Rendering Context.
Creat article, however, rotating a rectangular image is a bit more complex and I’m still unable to find a solution.