Canvas API Get Certified
The Canvas API lets you draw shapes, text, images, and animations directly onto a bitmap surface in the browser. Because this tutorial runs in a sandbox, the examples use a mock context that logs every drawing command so you can see exactly what would happen on a real canvas.
Getting the Drawing Context
You start by selecting a <canvas> element and
calling getContext("2d"). The returned object
contains all the drawing methods.
What this code does: Creates a mock 2D context and shows the basic pattern of obtaining it.
function createMockCanvasContext() {
function log(action, details) {
console.log("[canvas]", action, details !== undefined ? details : "");
}
return {
fillStyle: "#000000",
fillRect(x, y, w, h) {
log("fillRect", { x, y, w, h, fillStyle: this.fillStyle });
}
};
}
// On a real page you would do:
// const canvas = document.getElementById("myCanvas");
// const ctx = canvas.getContext("2d");
const ctx = createMockCanvasContext();
console.log("2D context ready");
ctx.fillStyle = "steelblue";
ctx.fillRect(10, 10, 100, 60);
getContext("2d").Drawing Rectangles
fillRect(), strokeRect(), and
clearRect() are the simplest drawing commands.
They work immediately without needing a path.
What this code does: Draws filled and outlined rectangles and demonstrates clearing an area.
function createMockCanvasContext() {
let state = { fillStyle: "#000000", strokeStyle: "#000000" };
function log(action, details) {
console.log("[canvas]", action, details !== undefined ? details : "");
}
return {
get fillStyle() { return state.fillStyle; },
set fillStyle(v) { state.fillStyle = v; },
get strokeStyle() { return state.strokeStyle; },
set strokeStyle(v) { state.strokeStyle = v; },
fillRect(x, y, w, h) { log("fillRect", { x, y, w, h, fillStyle: state.fillStyle }); },
strokeRect(x, y, w, h) { log("strokeRect", { x, y, w, h, strokeStyle: state.strokeStyle }); },
clearRect(x, y, w, h) { log("clearRect", { x, y, w, h }); }
};
}
const ctx = createMockCanvasContext();
ctx.fillStyle = "tomato";
ctx.fillRect(20, 20, 80, 50);
ctx.strokeStyle = "navy";
ctx.strokeRect(120, 20, 80, 50);
ctx.clearRect(40, 30, 40, 30);
fillStyle.strokeStyle.Paths and Lines
More complex shapes are built with a path. You start with
beginPath(), move the pen with
moveTo(), add lines with lineTo(),
and finally call stroke() or fill().
What this code does: Builds a simple line path and strokes it.
function createMockCanvasContext() {
let state = { strokeStyle: "#000000", lineWidth: 1 };
function log(action, details) {
console.log("[canvas]", action, details !== undefined ? details : "");
}
return {
get strokeStyle() { return state.strokeStyle; },
set strokeStyle(v) { state.strokeStyle = v; },
get lineWidth() { return state.lineWidth; },
set lineWidth(v) { state.lineWidth = v; },
beginPath() { log("beginPath"); },
moveTo(x, y) { log("moveTo", { x, y }); },
lineTo(x, y) { log("lineTo", { x, y }); },
stroke() { log("stroke", { strokeStyle: state.strokeStyle, lineWidth: state.lineWidth }); }
};
}
const ctx = createMockCanvasContext();
ctx.beginPath();
ctx.moveTo(20, 20);
ctx.lineTo(180, 20);
ctx.lineTo(100, 100);
ctx.strokeStyle = "darkorange";
ctx.lineWidth = 3;
ctx.stroke();
Closing a Path
closePath() connects the last point back to the
first point of the current path, which is useful for closed
shapes such as triangles.
What this code does: Draws a closed triangle
using closePath().
function createMockCanvasContext() {
let state = { fillStyle: "#000000", strokeStyle: "#000000" };
function log(action, details) {
console.log("[canvas]", action, details !== undefined ? details : "");
}
return {
get fillStyle() { return state.fillStyle; },
set fillStyle(v) { state.fillStyle = v; },
get strokeStyle() { return state.strokeStyle; },
set strokeStyle(v) { state.strokeStyle = v; },
beginPath() { log("beginPath"); },
moveTo(x, y) { log("moveTo", { x, y }); },
lineTo(x, y) { log("lineTo", { x, y }); },
closePath() { log("closePath"); },
fill() { log("fill", { fillStyle: state.fillStyle }); },
stroke() { log("stroke", { strokeStyle: state.strokeStyle }); }
};
}
const ctx = createMockCanvasContext();
ctx.beginPath();
ctx.moveTo(50, 20);
ctx.lineTo(20, 80);
ctx.lineTo(80, 80);
ctx.closePath(); // connects back to (50, 20)
ctx.fillStyle = "mediumseagreen";
ctx.fill();
ctx.strokeStyle = "darkgreen";
ctx.stroke();
This sequence draws a triangle: down to one corner, across to
another, then closePath() connects back to the
starting point.
Circles and Arcs
arc(x, y, radius, startAngle, endAngle) draws part
(or all) of a circle. Angles are given in radians, so a full
circle goes from 0 to Math.PI * 2.
What this code does: Draws circles and arcs
using arc with start and end angles in radians.
function createMockCanvasContext() {
let state = { fillStyle: "#000000", strokeStyle: "#000000" };
function log(action, details) {
console.log("[canvas]", action, details !== undefined ? details : "");
}
return {
get fillStyle() { return state.fillStyle; },
set fillStyle(v) { state.fillStyle = v; },
get strokeStyle() { return state.strokeStyle; },
set strokeStyle(v) { state.strokeStyle = v; },
beginPath() { log("beginPath"); },
arc(x, y, radius, startAngle, endAngle) {
log("arc", { x, y, radius, startAngle, endAngle });
},
fill() { log("fill", { fillStyle: state.fillStyle }); },
stroke() { log("stroke", { strokeStyle: state.strokeStyle }); }
};
}
const ctx = createMockCanvasContext();
// A full circle
ctx.beginPath();
ctx.arc(75, 75, 50, 0, Math.PI * 2);
ctx.fillStyle = "purple";
ctx.fill();
// A half circle (an arc)
ctx.beginPath();
ctx.arc(200, 75, 50, 0, Math.PI);
ctx.strokeStyle = "green";
ctx.stroke();
0 to Math.PI * 2 = full 360°.0 to Math.PI = 180° semicircle.Adding Text
Set the font property, then call
fillText(text, x, y) to draw text at a given
position.
What this code does: Renders text on the canvas
with fillText, controlling size and family through
the font property.
function createMockCanvasContext() {
let state = { fillStyle: "#000000", font: "10px sans-serif" };
function log(action, details) {
console.log("[canvas]", action, details !== undefined ? details : "");
}
return {
get fillStyle() { return state.fillStyle; },
set fillStyle(v) { state.fillStyle = v; },
get font() { return state.font; },
set font(v) { state.font = v; },
fillText(text, x, y) {
log("fillText", { text, x, y, font: state.font });
}
};
}
const ctx = createMockCanvasContext();
ctx.font = "24px Arial";
ctx.fillStyle = "black";
ctx.fillText("Hello, Canvas!", 20, 40);
Saving and Restoring State
save() pushes the current drawing style onto a
stack. restore() pops it back off, undoing any
changes made since the matching save().
What this code does: Uses
save() and restore() to temporarily
change styles without permanently altering the context state.
function createMockCanvasContext() {
let state = { fillStyle: "#000000" };
const stack = [];
function log(action, details) {
console.log("[canvas]", action, details !== undefined ? details : "");
}
return {
get fillStyle() { return state.fillStyle; },
set fillStyle(v) { state.fillStyle = v; },
fillRect(x, y, w, h) {
log("fillRect", { x, y, w, h, fillStyle: state.fillStyle });
},
save() {
stack.push({ ...state });
log("save");
},
restore() {
if (stack.length > 0) state = stack.pop();
log("restore", { fillStyle: state.fillStyle });
}
};
}
const ctx = createMockCanvasContext();
ctx.fillStyle = "red";
ctx.fillRect(10, 10, 50, 50);
ctx.save();
ctx.fillStyle = "blue";
ctx.fillRect(70, 10, 50, 50);
ctx.restore();
// Back to red
ctx.fillRect(130, 10, 50, 50);
Transformations
translate(), rotate(), and
scale() move, rotate, and resize everything drawn
afterward relative to the canvas’s coordinate system.
What this code does: Applies transformations so subsequent drawing commands are offset or rotated.
function createMockCanvasContext() {
let state = { fillStyle: "#000000" };
function log(action, details) {
console.log("[canvas]", action, details !== undefined ? details : "");
}
return {
get fillStyle() { return state.fillStyle; },
set fillStyle(v) { state.fillStyle = v; },
fillRect(x, y, w, h) {
log("fillRect", { x, y, w, h, fillStyle: state.fillStyle });
},
translate(x, y) { log("translate", { x, y }); },
rotate(angle) { log("rotate", { angle }); }
};
}
const ctx = createMockCanvasContext();
ctx.translate(50, 50);
ctx.rotate(Math.PI / 4); // 45 degrees
ctx.fillStyle = "seagreen";
ctx.fillRect(0, 0, 40, 40);
Math.PI / 4 radians = 45 degrees.On a real canvas this draws a square rotated 45 degrees, positioned 50 pixels right and down from the top-left corner.
Canvas vs. SVG
| Feature | Canvas | SVG |
|---|---|---|
| Drawing model | Immediate-mode (pixels) | Retained-mode (DOM elements) |
| Individual shapes stay interactive? | No, it’s just pixels | Yes, each shape is its own element |
| Performance with many objects | Better for thousands of objects | Can slow down with very many elements |
| Scales without quality loss? | No, resolution-dependent | Yes, vector-based |
| Typical use case | Games, animations, image processing | Icons, diagrams, interactive charts |
Common Mistakes with Canvas
- Forgetting
beginPath()before starting a new shape, which can cause old path segments to be redrawn. - Confusing
fill()andstroke()— one fills the interior, the other only draws the outline. - Forgetting
clearRect()at the start of each animation frame, leaving old frames visible as trails. - Setting the canvas size with CSS instead of its
width/heightattributes, which stretches and blurs the drawing. - Calling
save()without a matchingrestore(), leaving styles or transformations applied longer than intended.
Best Practices for Canvas
- Always pair
save()with a matchingrestore()around any temporary style or transformation. - Clear the canvas at the start of every frame for animations, unless a “trail” effect is intentional.
- Group related drawing logic into small functions (e.g.
drawBackground(),drawPlayer()). - Set canvas dimensions through its
widthandheightattributes, not CSS, to avoid blurry output. - Use
requestAnimationFrameinstead ofsetIntervalfor smooth, browser-optimized animations.
Quick Summary
canvas.getContext("2d")returns the object with all drawing methods.fillRect(),strokeRect(), andclearRect()draw or erase rectangles directly.- Other shapes are built with
beginPath()/moveTo()/lineTo(), finished withfill()orstroke(). arc()draws circles and partial arcs using radian angles.fillText()draws text using the currentfontandfillStyle.save()/restore()protect style changes from leaking into later drawing calls.translate(),rotate(), andscale()transform everything drawn afterward.
Try It Yourself
Modify the code below. Try changing the colors, adding a second
circle, or wrapping one of the shapes in
save()/restore() so it does not affect
the others.
What this code does: A practice scene that combines a background, a filled circle, and text — good for experimenting with styles and paths.
function createMockCanvasContext() {
let state = {
fillStyle: "#000000",
strokeStyle: "#000000",
font: "10px sans-serif"
};
const stack = [];
function log(action, details) {
console.log("[canvas]", action, details !== undefined ? details : "");
}
return {
get fillStyle() { return state.fillStyle; },
set fillStyle(v) { state.fillStyle = v; },
get strokeStyle() { return state.strokeStyle; },
set strokeStyle(v) { state.strokeStyle = v; },
get font() { return state.font; },
set font(v) { state.font = v; },
fillRect(x, y, w, h) {
log("fillRect", { x, y, w, h, fillStyle: state.fillStyle });
},
beginPath() { log("beginPath"); },
arc(x, y, radius, startAngle, endAngle) {
log("arc", { x, y, radius, startAngle, endAngle });
},
fill() { log("fill", { fillStyle: state.fillStyle }); },
fillText(text, x, y) {
log("fillText", { text, x, y, font: state.font });
},
save() {
stack.push({ ...state });
log("save");
},
restore() {
if (stack.length > 0) state = stack.pop();
log("restore");
}
};
}
const ctx = createMockCanvasContext();
// Background
ctx.fillStyle = "lightgray";
ctx.fillRect(0, 0, 300, 150);
// Circle
ctx.beginPath();
ctx.arc(80, 75, 40, 0, Math.PI * 2);
ctx.fillStyle = "coral";
ctx.fill();
// Text
ctx.font = "20px Arial";
ctx.fillStyle = "black";
ctx.fillText("My Canvas Scene", 140, 80);
Tip: Start every shape withbeginPath(), wrap temporary style changes insave()/restore(), and clear the canvas each frame when animating — these three habits prevent most common canvas bugs.
Understanding the Canvas API opens the door to building games,
charts, and custom visualizations directly in the browser, and
pairs well with requestAnimationFrame for smooth,
continuous animation.
🧪 Test Your JavaScript Code
Edit the code on the left and click “Run Code” to see the console output on the right.
Click “Run Code” to see the output here.
Code Explanation
The complete example stays visible. Use Previous and Next to move through its logical blocks.
Select Next to begin exploring the code.