context.save() / context.restore()

Overview

Overviewvisual1

The context.save() method saves the current drawing state of the canvas by pushing it onto a stack. The context.restore() method restores the most recently saved drawing state by popping it from the stack.

The drawing state is how the context remembers which fillStyle we are using and the position and orientation of the coordinate system. It consists of the current transformation matrix for the context's coordinate system, the current clipping region, the current dash list used for drawing dashed lines, and the current values stored in the following properties:

  • context.fillStyle
  • context.strokeStyle
  • context.lineWidth
  • context.lineCap
  • context.lineJoin
  • context.miterLimit
  • context.lineDashOffset
  • context.font
  • context.textAlign
  • context.textBaseline
  • context.direction
  • context.shadowOffsetX
  • context.shadowOffsetY
  • context.shadowBlur
  • context.shadowColor
  • context.globalAlpha
  • context.globalCompositeOperation
  • context.imageSmoothingEnabled

We can save the current drawing state using the context.save() method. This adds the current drawing state to the top of a stack of drawing states. Then, when we use the context.restore() method, the drawing state at the top of the stack is restored.

Save and Restore a Drawing State

In this example, we set the context.fillStyle property to the color 'LightSkyBlue' and draw a filled rectangle using the context.fillRect() method.

context.fillStyle = 'LightSkyBlue';
context.fillRect(20, 20, 320, 240);

At this point, we save the current drawing state to the stack. In the current drawing state, context.fillStyle = 'LightSkyBlue'.

context.save();

After saving the current drawing state, we change the context.fillStyle property to the color 'Orchid' and draw second filled rectangle on top of the first.

context.fillStyle = 'Orchid';
context.fillRect(60, 60, 200, 160);

Now, we want to draw another light sky blue rectangle on top of the orchid rectangle. We could manually change the context.fillStyle property back to 'LightSkyBlue' or we can restore the previous drawing state by using the context.restore() method.

context.restore();
context.fillRect(100, 80, 120, 120);

This restores the drawing state back to its most recently saved state, when context.fillStyle = 'LightSkyBlue', so the third rectangle is the same color as the first.

To learn more about setting fill colors and drawing rectangles, visit the fillStyle and fillRect() lessons.

Quick Reference: Coordinates fillRect() fillStyle

Editor (write code below)
var canvas = document.getElementById('save_example1'); var context = canvas.getContext('2d'); context.fillStyle = 'LightSkyBlue'; context.fillRect(20, 20, 320, 240); context.save(); // Save the current drawing state context.fillStyle = 'Orchid'; context.fillRect(60, 60, 200, 160); context.restore(); // Restore the most recently saved drawing state context.fillRect(100, 80, 120, 120);
Message Log
This is a lesson, not a challenge, the code runs automatically.

But change it! Play with it! Click "Run" to see your changes.

Run
Run and Focus Canvas
Reset
Canvas (your drawing will display here)

Challenge 1

Challenge1visual1
What your drawing should look like

Rearrange the lines of code below to draw the image to the right.

Draw the large gray rectangle first and save the drawing state. Draw the two green rectangles next. Then, restore the drawing state to draw the three small gray rectangles. As long as the code is placed in the correct order, you should not have to type any code yourself.

Editor (write code below)
var canvas = document.getElementById('save_challenge1'); var context = canvas.getContext('2d'); context.save(); context.restore(); context.fillStyle = 'Gray'; context.fillStyle = 'MediumSeaGreen'; // Large gray rectangle context.fillRect(20, 20, 320, 240); // Two green rectangles context.fillRect(40, 40, 130, 200); context.fillRect(190, 40, 130, 200); // Three small gray rectangles context.fillRect(60, 60, 90, 70); context.fillRect(60, 150, 90, 70); context.fillRect(210, 60, 90, 160);
Message Log
This is a lesson, not a challenge, the code runs automatically.

But change it! Play with it! Click "Run" to see your changes.

Run
Run and Focus Canvas
Reset
Canvas (your drawing will display here)
Challenge1

Restore the Drawing State After Executing a Function

In this example, we set the context.fillStyle to the color 'BurlyWood' and draw a filled rectangle.

context.fillStyle = 'BurlyWood';
context.fillRect(40, 20, 360, 80);

We then use the drawPurpleText() function to draw a purple text string on top of the rectangle.

drawPurpleText(50, 70, 'This text is purple!');

The next time we draw a filled rectangle, it would be natural for us to think it will be filled with the color 'BurlyWood'. After all, we haven't changed the fillStyle since drawing the first rectangle. But we would be wrong. The drawPurpleText() function has changed the context.fillStyle property and the context.font property.

function drawPurpleText(x, y, text) {
  context.font = '36px Arial';
  context.fillStyle = 'RebeccaPurple';
  context.fillText(text, x, y);
}

In general, a function should be self-contained. If it makes changes to the drawing state, it should undo those changes immediately once the function is done. Otherwise, it can be difficult for the programmer to know the exact state of the canvas at all times.

The simplest way to ensure a function undoes its changes to the drawing state is to wrap the code in the function with a context.save() at the start of the function and a context.restore() at the end. The context.save() saves the drawing state before the function makes any changes, and then the context.restore() restores it.

function drawPurpleText(x, y, text) {
  context.save();
  context.font = '36px Arial';
  context.fillStyle = 'RebeccaPurple';
  context.fillText(text, x, y);
  context.restore();
}

If you wrap the code in the drawPurpleText() function with a context.save() and context.restore(), the second rectangle will now be filled with the color 'BurlyWood', which is what we would expect.

To learn more about functions, visit the Functions lesson.

Quick Reference: Coordinates Functions fillRect() fillStyle

Editor (write code below)
var canvas = document.getElementById('save_example2'); var context = canvas.getContext('2d'); context.fillStyle = 'BurlyWood'; context.fillRect(40, 20, 360, 80); drawPurpleText(50, 70, 'This text is purple!'); context.fillRect(40, 120, 360, 80); drawPurpleText(50, 170, 'This function is neat!'); function drawPurpleText(x, y, text) { context.font = '36px Arial'; context.fillStyle = 'RebeccaPurple'; context.fillText(text, x, y); }
Message Log
This is a lesson, not a challenge, the code runs automatically.

But change it! Play with it! Click "Run" to see your changes.

Run
Run and Focus Canvas
Reset
Canvas (your drawing will display here)

Challenge 2

Challenge2visual1
What your drawing should look like

The fillCircle() function draws a circle centered at coordinates (x, y) with radius r, and fills it with the color currently stored in the context.fillStyle property.

function fillCircle(x, y, r) {
  context.beginPath();
  context.arc(x, y, r, 0, 2 * Math.PI, false);
  context.closePath();
  context.fill();
}

Copy the code from the fillCircle() function defintion into the fillWhiteCircle() function definition. But, instead of filling the circle using the current fillStyle, fill it with the color 'White'.

If your fillWhiteCircle() function is self-contained and undoes any changes made to the drawing state once it is done, then you will draw the image to the right.

Editor (write code below)
var canvas = document.getElementById('save_challenge2'); var context = canvas.getContext('2d'); function fillCircle(x, y, r) { context.beginPath(); context.arc(x, y, r, 0, 2 * Math.PI, false); context.closePath(); context.fill(); } function fillWhiteCircle(x, y, r) { // COPY THE CODE FROM THE fillCircle() FUNCTION HERE // EDIT THE CODE SO IT FILLS THE CIRCLE WITH THE COLOR WHITE INSTEAD OF THE CURRENT FILLSTYLE } context.fillStyle = 'DarkOrange'; fillCircle(210, 100, 80); fillWhiteCircle(210, 100, 60); fillCircle(210, 100, 40); fillWhiteCircle(210, 100, 20); context.fillStyle = 'SaddleBrown'; fillCircle(100, 220, 80); fillWhiteCircle(100, 220, 60); fillCircle(100, 220, 40); fillWhiteCircle(100, 220, 20); context.fillStyle = 'FireBrick'; fillCircle(320, 220, 80); fillWhiteCircle(320, 220, 60); fillCircle(320, 220, 40); fillWhiteCircle(320, 220, 20);
Message Log
This is a lesson, not a challenge, the code runs automatically.

But change it! Play with it! Click "Run" to see your changes.

Run
Run and Focus Canvas
Reset
Canvas (your drawing will display here)
Challenge2

Save and Restore Multiple Drawing States

Example3visual1

Because we are saving drawing states to a stack, we can save multiple states before restoring them later. The main thing to remember is that, when restoring a drawing state, we always restore the most recently saved drawing state first.

In this example, we start by saving the original drawing state. Just like we save and then restore the drawing state in a function, it's also a good idea to save and restore the drawing state in a program. It's especially important when we are animating a drawing by drawing over the same canvas over and over again.

Each time we save a drawing state, it gets added to the top of the stack. The original drawing state is on the bottom of the stack because it was saved first. By the time we restore a drawing state, there are four drawing states on the stack. The first drawing state restored is drawing state C because it is on top of the stack as the most recently saved drawing state. We restore the original drawing state last at the end of the program.

If it helps, think of the context.save() and context.restore() methods like opening and closing parentheses. Just like parentheses can be nested inside of other parentheses, we can nest a context.save() and context.restore() inside of another context.save() andcontext.restore()as long as they are paired.

Quick Reference: Coordinates fillRect() fillStyle

Editor (write code below)
var canvas = document.getElementById('save_example3'); var context = canvas.getContext('2d'); context.save(); // Save original drawing state context.fillStyle = 'DarkViolet'; context.fillRect(20, 20, 320, 240); drawText(25, 251, 'Drawing State A'); context.save(); // Save drawing state A context.fillStyle = 'DarkSlateBlue'; context.fillRect(20, 20, 290, 210); drawText(25, 221, 'Drawing State B'); context.save(); // Save drawing state B context.fillStyle = 'ForestGreen'; context.fillRect(20, 20, 260, 180); drawText(25, 191, 'Drawing State C'); context.save(); // Save drawing state C context.fillStyle = 'Brown'; context.fillRect(20, 20, 230, 150); context.restore(); // Restore drawing state C context.fillRect(20, 20, 200, 120); drawText(25, 131, 'Drawing State C'); context.restore(); // Restore drawing state B context.fillRect(20, 20, 170, 90); drawText(25, 101, 'Drawing State B'); context.restore(); // Restore drawing state A context.fillRect(20, 20, 140, 60); drawText(25, 71, 'Drawing State A'); context.restore(); // Restore original drawing state function drawText(x, y, text) { context.save(); context.fillStyle = 'White'; context.font = '16px Arial'; context.fillText(text, x, y); context.restore(); }
Message Log
This is a lesson, not a challenge, the code runs automatically.

But change it! Play with it! Click "Run" to see your changes.

Run
Run and Focus Canvas
Reset
Canvas (your drawing will display here)

Challenge 3

Challenge3visual1
What your drawing should look like

Without changing the order of the lines of code in the program below, add calls to the context.save() and context.restore() methods so the program draws the image to the right.

Think about the colors stored in the context.fillStyle property you will need to save so you can restore and use them later.

Editor (write code below)
var canvas = document.getElementById('save_challenge3'); var context = canvas.getContext('2d'); context.save(); // Save original drawing state context.fillStyle = 'Red'; context.fillRect(10, 20, 40, 240); context.fillStyle = 'Orange'; context.fillRect(50, 20, 40, 240); context.fillStyle = 'Yellow'; context.fillRect(90, 20, 40, 240); context.fillStyle = 'Green'; context.fillRect(130, 20, 40, 240); context.fillStyle = 'Blue'; context.fillRect(170, 20, 40, 240); context.fillStyle = 'Indigo'; context.fillRect(210, 20, 40, 240); context.fillStyle = 'Violet'; context.fillRect(250, 20, 40, 240); context.fillRect(290, 20, 40, 240); context.fillRect(330, 20, 40, 240); context.fillRect(370, 20, 40, 240); context.restore(); // Restore original drawing state
Message Log
This is a lesson, not a challenge, the code runs automatically.

But change it! Play with it! Click "Run" to see your changes.

Run
Run and Focus Canvas
Reset
Canvas (your drawing will display here)
Challenge3

Save and Restore the Coordinate System

The drawing state does more than store the value of the context.fillStyle property. It also stores the current transformation matrix for the coordinate system.

In this example, we position squares to form the letter F by using the context.translate() method to move the origin of the coordinate system instead of drawing the squares at specific coordinates.

We start by saving the original drawing state and setting the context.fillStyle property to the color 'DeepPink'.

context.save(); // Save original drawing state
context.fillStyle = 'DeepPink';

Then, we move the origin of the coordinate system to (40, 40). This is where we will start forming the letter F.

context.translate(40, 40);

The plan is to draw five squares in a row straight down to form the back of the letter F. But before we do, we save the drawing state so we can easily return to this position at the top of the letter later.

context.save(); // Save drawing state A

To draw the five squares down the back of the letter F, we draw a square, move the origin of the coordinate system down 50 pixels, draw another square, move the origin down another 50 pixels, etc. By moving the coordinate system, each square is actually drawn at (0, 0).

context.fillRect(0, 0, 45, 45);
context.translate(0, 50);
context.fillRect(0, 0, 45, 45);
context.translate(0, 50);
context.save(); // Save drawing state B
context.fillRect(0, 0, 45, 45);
context.translate(0, 50);
context.fillRect(0, 0, 45, 45);
context.translate(0, 50);
context.fillRect(0, 0, 45, 45);

After translating the coordinate system to draw the third square, we save the drawing state again. Why? This is so we can return to this position later to draw the horizontal bar in the middle of the F.

Once we reach the bottom of the letter F and draw the fifth square, we use the context.restore() method to return to the middle of the letter where we want to draw the horizontal bar.

context.restore(); // Restore drawing state B

To draw the horizontal bar in the middle of the letter F, we translate 50 pixels to the right and draw a square. Then, we use the context.restore() method again to return to the top of the letter.

context.translate(50, 0);
context.fillRect(0, 0, 45, 45);
context.restore(); // Restore drawing state A

The only thing left to do is draw the horizontal bar at the top of the letter F by drawing two more squares in a row, translating 50 pixels to the right each time. With the letter complete, we use the context.restore() method one last time to restore the original drawing state.

context.translate(50, 0);
context.fillRect(0, 0, 45, 45);
context.translate(50, 0);
context.fillRect(0, 0, 45, 45);
context.restore(); // Restore original drawing state

If you are having a hard time figuring out where the origin of the coordinate system is after a translation, use the drawBlackDotAtOrigin() function to draw a black dot at the origin.

To learn more about translating the coordinate system, visit the Coordinates and translate() lessons.

Quick Reference: Coordinates Functions fillRect() fillStyle translate()

Editor (write code below)
var canvas = document.getElementById('save_example4'); var context = canvas.getContext('2d'); context.save(); // Save original drawing state context.fillStyle = 'DeepPink'; context.translate(40, 40); context.save(); // Save drawing state A context.fillRect(0, 0, 45, 45); context.translate(0, 50); context.fillRect(0, 0, 45, 45); context.translate(0, 50); context.save(); // Save drawing state B context.fillRect(0, 0, 45, 45); context.translate(0, 50); context.fillRect(0, 0, 45, 45); context.translate(0, 50); context.fillRect(0, 0, 45, 45); context.restore(); // Restore drawing state B context.translate(50, 0); context.fillRect(0, 0, 45, 45); context.restore(); // Restore drawing state A context.translate(50, 0); context.fillRect(0, 0, 45, 45); context.translate(50, 0); context.fillRect(0, 0, 45, 45); context.restore(); // Restore original drawing state function drawBlackDotAtOrigin() { context.save(); context.fillStyle = 'Black'; context.beginPath(); context.arc(0, 0, 4, 0, 2 * Math.PI, false); context.closePath(); context.fill(); context.restore(); }
Message Log
This is a lesson, not a challenge, the code runs automatically.

But change it! Play with it! Click "Run" to see your changes.

Run
Run and Focus Canvas
Reset
Canvas (your drawing will display here)

Challenge 4

Challenge4visual1
What your drawing should look like

Without changing the order of the lines of code in the program below, add calls to the context.save() and context.restore() methods so the program draws the image to the right.

Right now, the program is drawing five squares in a row vertically down, two squares in a row horizontally to the right, two squares in a row vertically up, and finally two squares in a row vertically down.

Think about the positions you will need to save so you can restore and return to them later. If you are having a hard time figuring out where the origin of the coordinate system is after a translation, use the drawBlackDotAtOrigin() function to draw a black dot at the origin.

Editor (write code below)
var canvas = document.getElementById('save_challenge4'); var context = canvas.getContext('2d'); context.save(); // Save original drawing state context.fillStyle = 'DodgerBlue'; context.translate(40, 40); context.fillRect(0, 0, 45, 45); context.translate(0, 50); context.fillRect(0, 0, 45, 45); context.translate(0, 50); context.fillRect(0, 0, 45, 45); context.translate(0, 50); context.fillRect(0, 0, 45, 45); context.translate(0, 50); context.fillRect(0, 0, 45, 45); context.translate(50, 0); context.fillRect(0, 0, 45, 45); context.translate(50, 0); context.fillRect(0, 0, 45, 45); context.translate(0, -50); context.fillRect(0, 0, 45, 45); context.translate(0, -50); context.fillRect(0, 0, 45, 45); context.translate(0, 50); context.fillRect(0, 0, 45, 45); context.translate(0, 50); context.fillRect(0, 0, 45, 45); context.restore(); // Restore original drawing state function drawBlackDotAtOrigin() { context.save(); context.fillStyle = 'Black'; context.beginPath(); context.arc(0, 0, 4, 0, 2 * Math.PI, false); context.closePath(); context.fill(); context.restore(); }
Message Log
This is a lesson, not a challenge, the code runs automatically.

But change it! Play with it! Click "Run" to see your changes.

Run
Run and Focus Canvas
Reset
Canvas (your drawing will display here)
Challenge4

Restore the Same Drawing State Multiple Times

There are situations where we want to restore to the same drawing state more than once. This is simple to do. We just have to remember that, when we restore a drawing state, we pop it off the stack. So, if we want to restore to that same state again later, we need to re-save it to push it back onto the stack.

In this example, we use the context.translate() method to draw squares in the form of the letter K. To form the letter K, we have to return to the same position in the middle of the vertical line twice, once to draw the diagonal line up and again to draw the diagonal line down.

We start by saving the original drawing state, setting the context.fillStyle property to the color 'Orange', and then translating to the top of the K at (40, 40).

context.save(); // Save original drawing state
context.fillStyle = 'Orange';
context.translate(40, 40);

Then, we draw five squares in a row vertically down, translating 50 pixels down each time. After moving the origin of the coordinate system to the position of the third square, we save the drawing state so we can return to that position later.

context.fillRect(0, 0, 45, 45);
context.translate(0, 50);
context.fillRect(0, 0, 45, 45);
context.translate(0, 50);
context.save(); // Save drawing state A
context.fillRect(0, 0, 45, 45);
context.translate(0, 50);
context.fillRect(0, 0, 45, 45);
context.translate(0, 50);
context.fillRect(0, 0, 45, 45);

To draw the diagonal line up, we restore drawing state A, moving the origin of the coordinate system back to the position of the third block. Since we know we will have to return to this position again, we immediately re-save drawing state A. We form the diagonal line up by translating 50 pixels up and 50 pixels to the right.

context.restore(); // Restore drawing state A
context.save(); // Re-save drawing state A
context.translate(50, -50);
context.fillRect(0, 0, 45, 45);
context.translate(50, -50);
context.fillRect(0, 0, 45, 45);

To draw the diagonal line down, we repeat the process, restoring drawing state A again. We form the diagonal line down by translating 50 pixels down and 50 pixels to the right. Then, with the letter K complete, we restore the original drawing state.

context.restore(); // Restore drawing state A again
context.translate(50, 50);
context.fillRect(0, 0, 45, 45);
context.translate(50, 50);
context.fillRect(0, 0, 45, 45);
context.restore(); // Restore original drawing state

If you are having a hard time figuring out where the origin of the coordinate system is after a translation, use the drawBlackDotAtOrigin() function to draw a black dot at the origin.

Quick Reference: Coordinates Functions fillRect() fillStyle translate()

Editor (write code below)
var canvas = document.getElementById('save_example5'); var context = canvas.getContext('2d'); context.save(); // Save original drawing state context.fillStyle = 'Orange'; context.translate(40, 40); context.fillRect(0, 0, 45, 45); context.translate(0, 50); context.fillRect(0, 0, 45, 45); context.translate(0, 50); context.save(); // Save drawing state A context.fillRect(0, 0, 45, 45); context.translate(0, 50); context.fillRect(0, 0, 45, 45); context.translate(0, 50); context.fillRect(0, 0, 45, 45); context.restore(); // Restore drawing state A context.save(); // Re-save drawing state A context.translate(50, -50); context.fillRect(0, 0, 45, 45); context.translate(50, -50); context.fillRect(0, 0, 45, 45); context.restore(); // Restore drawing state A again context.translate(50, 50); context.fillRect(0, 0, 45, 45); context.translate(50, 50); context.fillRect(0, 0, 45, 45); context.restore(); // Restore original drawing state function drawBlackDotAtOrigin() { context.save(); context.fillStyle = 'Black'; context.beginPath(); context.arc(0, 0, 4, 0, 2 * Math.PI, false); context.closePath(); context.fill(); context.restore(); }
Message Log
This is a lesson, not a challenge, the code runs automatically.

But change it! Play with it! Click "Run" to see your changes.

Run
Run and Focus Canvas
Reset
Canvas (your drawing will display here)

Challenge 5

Challenge5visual1
What your drawing should look like

Without changing the order of the lines of code in the program below, add calls to the context.save() and context.restore() methods so the program draws the image to the right.

Right now, the program is drawing five squares in a row diagonally down, two squares in a row diagonally up, and two squares in a row diagonally down and to the left.

Think about the positions you will need to save so you can restore and return to them later. If you are having a hard time figuring out where the origin of the coordinate system is after a translation, use the drawBlackDotAtOrigin() function to draw a black dot at the origin.

Editor (write code below)
var canvas = document.getElementById('save_challenge5'); var context = canvas.getContext('2d'); context.save(); // Save original drawing state context.fillStyle = 'Crimson'; context.translate(40, 40); context.fillRect(0, 0, 45, 45); context.translate(50, 50); context.fillRect(0, 0, 45, 45); context.translate(50, 50); context.fillRect(0, 0, 45, 45); context.translate(50, 50); context.fillRect(0, 0, 45, 45); context.translate(50, 50); context.fillRect(0, 0, 45, 45); context.translate(50, -50); context.fillRect(0, 0, 45, 45); context.translate(50, -50); context.fillRect(0, 0, 45, 45); context.translate(-50, 50); context.fillRect(0, 0, 45, 45); context.translate(-50, 50); context.fillRect(0, 0, 45, 45); context.restore(); // Restore original drawing state function drawBlackDotAtOrigin() { context.save(); context.fillStyle = 'Black'; context.beginPath(); context.arc(0, 0, 4, 0, 2 * Math.PI, false); context.closePath(); context.fill(); context.restore(); }
Message Log
This is a lesson, not a challenge, the code runs automatically.

But change it! Play with it! Click "Run" to see your changes.

Run
Run and Focus Canvas
Reset
Canvas (your drawing will display here)
Challenge5

Ready for the next lesson?

Next up, the "translate()" lesson >