We are going to talk about a very important feature of the canvas element.
It's the possibility to save and restore the context.
So, if you want to draw some shapes at a given position, with a given color, with given gradients, textures, shadows, line widths, etc., maybe you don't want to affect other functions that will use different context property values.
So, let's see an example...
I'm going just to draw a small shape and place it somewhere on the screen, with some colors and see what kind of problems can occur, and how using the context save and restore methods can help us.
So, I start by defining a canvas element, I give it an ID, a width, and a height.
Ok...I would like to see it, so I'm adding a few CSS rules in a style element… just for seeing the border. This is also a good trick... I do this regularly...
Ok... like that I can see the border of the canvas here.
Now I'm going to write some JavaScript for drawing a shape inside.
I'm adding a script element here, after the canvas... and I'm getting the canvas.
Right...now I'm getting the context...
Like this - and now I can try to draw something.
I'm drawing a rectangle at position (0, 0), I can see it... so, the default color is black because there is no context settings so far, I haven't changed any color, and the rectangle is at the top left, at the original coordinate system position.
And now, I'm going to write a function that will draw a small monster.
You know I like monsters.... I want to draw it at a given position, with a given color and also a given angle.
Let's try this... so a good practice is to start drawing it like if it was in (0, 0) position.
I'm going to change the color... fillStyle... equals the color parameter.... and I'm going to change the coordinate system so that the monster will be drawn at a x and y position instead of (0, 0)... this is the ctx.translate(...).
I'm changing the coordinate system, and I'm changin the color here... and now I'm drawing something. a rectangle, at position (0, 0). Remember that this (0, 0) will be different on the screen because of the translate function.
Ok, let's give it 200 pixels width, with 200 pixel height.
And I'm calling this code before the red rectangle.
So, the function is called drawMonster(..) and ... let's draw it at (100, 100)... with an angle of zero for the moment, and a color 'red'. Ok. So I got a red rectangle here ... and I also forgot the angle, I'm adding a ctx.rotate(angle)...
I'm rotating the coordinate system. I passed a zero angle, but I can pass for example Math.PI/4 and I've got a rotated rectangle.
Let's put it somewhere else... so that we can see it... but... where is the black rectangle that was originally at position (0, 0)?
If I do that, I can see it here, but you can see that the drawMonster (...) function affected the coordinate system position, and the color of the other rectangle.
It's because I modified here the position of the coordinate system.
I modified the color here.
In order to leave the context unaffected by the drawMonster function, -and this is a good practice- : I save the context before and I restore it at the end.
In that case, you can see that the rectangle that is drawn after the call to drawMonster is still at its original position, with its original coordinate system, with its default color that is black.
This is really a good practice!
Save the context before doing anything in a function that changes its properties, and restore it at the end!
Be careful if you save it and if you do not restore it, you may have unpredictable results!
Always have a save associated with a restore!
This is interesting because now I can draw several monsters... I can add another one here, change the position, for example at (15, 15).
Change the color... change the angle... 0.1 radiant.
You see that you can write functions that will do lots of things and leave everything unaffected.
This is the end... I hope you enjoyed the example. The rest of the page will describe more in details what I explained here in the video, if you prefer to read instead of watching again this video...
Bye bye!
There are two methods for saving and restoring the context properties: ctx.save()and ctx.restore().
What will be saved: fillStyle and strokeStyle, lineWidth, the previous coordinate system, etc. That is ALL properties that affect drawing!
A call to ctx.save() will probably save the context property values in a hardware register on your graphics card. Multiple contexts can be saved consecutively and restored.
Multiple contexts can be backed up consecutively and restored. Contexts saved will be stacked, the last one that has been saved will be restored by the next call to restore(), so it is very important to have one restore for each save.
We slightly modified the function that draws the monster:
We added parameters for setting the position and orientation of the monster, and added calls to ctx.translate(x, y) and ctx.rotate(angle) in the function.
We added parameters for the head color and eye color.
We saved the context at the beginning of the function (BEST PRACTICE),
We restored it at the end (BEST PRACTICE).
Source code extract of this function: notice at lines 3 and 26 how we save/restore the context at the beginning/end. Right after saving the context, we modify the coordinate system (lines 7-8). The rest of the code is nearly the same as in the last version of the monster example.
function drawMonster(x, y, angle, headColor, eyeColor) {
// BEST PRACTICE: SAVE CONTEXT AND RESTORE IT AT THE END
ctx.save();
// Moves the coordinate system so that the monster is drawn
// at position (x, y)
ctx.translate(x, y);
ctx.rotate(angle);
// head
ctx.fillStyle=headColor;
ctx.fillRect(0,0,200,200);
// eyes
ctx.fillStyle='red';
ctx.fillRect(35,30,20,20);
ctx.fillRect(140,30,20,20);
// interior of eye
ctx.fillStyle=eyeColor;
ctx.fillRect(43,37,10,10);
ctx.fillRect(143,37,10,10);
...
// BEST PRACTICE!
ctx.restore();
}