Academic Block

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);

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);

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();

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);

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() and stroke() — 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/height attributes, which stretches and blurs the drawing.
  • Calling save() without a matching restore(), leaving styles or transformations applied longer than intended.

Best Practices for Canvas

  • Always pair save() with a matching restore() 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 width and height attributes, not CSS, to avoid blurry output.
  • Use requestAnimationFrame instead of setInterval for smooth, browser-optimized animations.

Quick Summary

  • canvas.getContext("2d") returns the object with all drawing methods.
  • fillRect(), strokeRect(), and clearRect() draw or erase rectangles directly.
  • Other shapes are built with beginPath() / moveTo() / lineTo(), finished with fill() or stroke().
  • arc() draws circles and partial arcs using radian angles.
  • fillText() draws text using the current font and fillStyle.
  • save() / restore() protect style changes from leaking into later drawing calls.
  • translate(), rotate(), and scale() 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 with beginPath(), wrap temporary style changes in save()/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.

📝 JavaScript Code
👁 Console Output

Click “Run Code” to see the output here.

Ctrl + Enter to run code Esc to close