CodingNic

Browser APIs

Canvas API Introduction

Browser APIs 20 min read

Canvas API Introduction

Objectives

By the end of this chapter, you should be able to:

  • Get a drawing context from a <canvas> element
  • Draw filled shapes and text
  • Draw a photo onto a canvas with drawImage()
  • Redraw a canvas from scratch in response to a user action

๐Ÿ’ก Why this matters: Everything drawn so far in this course has been HTML elements, boxes, text, styled with CSS. Canvas is different: it’s a blank rectangle you draw directly onto, pixel by pixel, the foundation behind charts, games, and image editing in the browser.

โš ๏ธ A note on verification: the <canvas> element itself, its width and height, was verified directly in this course’s testing setup, that part behaves like any other DOM element. Actually drawing onto it needs a real browser’s rendering engine, which this sandbox doesn’t have. The drawing code below is accurate, based on the standardized Canvas API, run it yourself in a browser to see it firsthand.

What You’re Building

A profile badge generator: a circular badge with a colored background and the user’s initials, regenerated with a new random color every time a button is clicked, then upgraded to use a real photo instead.

text
profile-badge/
โ”œโ”€โ”€ index.html
โ”œโ”€โ”€ script.js
โ””โ”€โ”€ avatar.jpg
html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Profile Badge Generator</title>
</head>
<body>
  <h1>Profile Badge Generator</h1>
  <canvas id="badgeCanvas" width="200" height="200"></canvas>
  <br>
  <input id="initialsInput" type="text" placeholder="Your initials" maxlength="2">
  <button id="generateBtn">Generate Badge</button>

  <script src="script.js"></script>
</body>
</html>

avatar.jpg sits alongside index.html, it’s loaded and drawn onto the canvas in Step 6, not referenced with an <img> tag in the HTML itself. Everything else from here on goes in script.js.

Step 1: Get the Drawing Context

A canvas can’t be drawn on directly. .getContext("2d") returns a context object, everything drawing-related happens through it.

javascript
const canvas = document.getElementById("badgeCanvas");
const ctx = canvas.getContext("2d");

ctx is the object every step below uses. "2d" is by far the most common context, there’s also "webgl" for 3D graphics, out of scope here.

Step 2: Draw the Circular Background

Canvas has no fillCircle(), circles are drawn with .arc() as part of a path.

javascript
function drawBackground(color) {
  ctx.beginPath();
  ctx.arc(100, 100, 90, 0, Math.PI * 2);
  ctx.fillStyle = color;
  ctx.fill();
}

drawBackground("#4a90d9");

arc(x, y, radius, startAngle, endAngle) traces a circular path, centered at (x, y), here the middle of the 200x200 canvas. Angles are in radians, 0 to Math.PI * 2 draws a complete circle. beginPath() starts a new shape, fill() actually paints it using the current fillStyle.

Step 3: Add the Initials

javascript
function drawInitials(initials) {
  ctx.font = "bold 60px Arial";
  ctx.fillStyle = "white";
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";
  ctx.fillText(initials.toUpperCase(), 100, 100);
}

drawInitials("jr");

fillText(text, x, y) draws text with its position controlled by textAlign/textBaseline, set to "center" and "middle" here so the text centers itself on (100, 100) instead of starting there. Without those two lines, the initials would be drawn starting at that point instead of centered on it, worth trying both ways to see the difference.

Step 4: Combine Both Into One Badge

javascript
function drawBadge(color, initials) {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  drawBackground(color);
  drawInitials(initials);
}

drawBadge("#4a90d9", "jr");

clearRect() wipes the canvas before drawing, nothing drawn to a canvas erases automatically, so redrawing without clearing first would stack a new badge right on top of the old one. drawBadge() is now the single function that produces a complete badge from scratch, exactly what the “Generate Badge” button will call.

Step 5: Wire Up the Button

javascript
const generateBtn = document.getElementById("generateBtn");
const initialsInput = document.getElementById("initialsInput");

function randomColor() {
  const hue = Math.floor(Math.random() * 360);
  return `hsl(${hue}, 70%, 50%)`;
}

generateBtn.addEventListener("click", () => {
  const initials = initialsInput.value.trim() || "??";
  drawBadge(randomColor(), initials);
});

Every click reads the current initials from the input (falling back to "??" if it’s empty, the same guard pattern from Module 2’s mini project), picks a new random color, and calls drawBadge() to redraw the whole thing from a blank canvas. hsl(hue, 70%, 50%) is a quick way to get a wide range of distinct, evenly bright colors just by changing one number.

Step 6: Draw a Real Photo Instead

A solid color is a fine fallback, but a real profile badge usually shows an actual photo. drawImage() draws an image onto the canvas, and .clip() restricts drawing to a circular area, so a square photo comes out perfectly round, matching the badge’s shape.

javascript
function drawPhotoBadge(imageSrc, initials) {
  const photo = new Image();

  photo.onload = () => {
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    ctx.save();
    ctx.beginPath();
    ctx.arc(100, 100, 90, 0, Math.PI * 2);
    ctx.clip();
    ctx.drawImage(photo, 10, 10, 180, 180);
    ctx.restore();

    drawInitials(initials);
  };

  photo.src = imageSrc;
}

drawPhotoBadge("avatar.jpg", "jr");

Loading an image isn’t instant, photo.onload is a plain DOM event (Module 2), it fires once avatar.jpg has actually finished loading, and everything that depends on the image being ready goes inside that handler. ctx.save() remembers the canvas’s current state, ctx.clip() then restricts every drawing operation that follows to inside the circular path just traced, so drawImage() only paints inside that circle, not the full square photo. ctx.restore() undoes the clip afterward, so anything drawn after this function (like the initials, drawn as plain text over the top, unaffected by the circular clip) isn’t restricted by it too.

The Complete script.js

javascript
const canvas = document.getElementById("badgeCanvas");
const ctx = canvas.getContext("2d");
const generateBtn = document.getElementById("generateBtn");
const initialsInput = document.getElementById("initialsInput");

function drawBackground(color) {
  ctx.beginPath();
  ctx.arc(100, 100, 90, 0, Math.PI * 2);
  ctx.fillStyle = color;
  ctx.fill();
}

function drawInitials(initials) {
  ctx.font = "bold 60px Arial";
  ctx.fillStyle = "white";
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";
  ctx.fillText(initials.toUpperCase(), 100, 100);
}

function drawBadge(color, initials) {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  drawBackground(color);
  drawInitials(initials);
}

function randomColor() {
  const hue = Math.floor(Math.random() * 360);
  return `hsl(${hue}, 70%, 50%)`;
}

generateBtn.addEventListener("click", () => {
  const initials = initialsInput.value.trim() || "??";
  drawBadge(randomColor(), initials);
});

drawPhotoBadge() from Step 6 isn’t wired into generateBtn here, it’s a separate function you can call directly (as in the Try It below) to see the photo version work, without needing a second button on the page.

Try It

These can’t run in this course’s tooling, write the code and, if you have a moment, try it in an actual browser.

  1. Build the full badge generator from this lesson: drawBackground(), drawInitials(), drawBadge(), and the generateBtn click handler.
  2. Add a thin white border around the circle using strokeStyle, lineWidth, and stroke() inside drawBackground(), after the fill() call.
  3. Build drawPhotoBadge() from Step 6, and call it directly with a real image file’s path to see the clipped photo badge.
  4. Add a second button, "Use Photo", that calls drawPhotoBadge("avatar.jpg", initialsInput.value.trim() || "??") instead of the solid-color version.

Recap

  • <canvas> is a blank rectangle sized with width/height attributes. .getContext("2d") returns the object everything drawing-related happens through.
  • Circles use beginPath(), arc(x, y, radius, startAngle, endAngle), and fill(), this lesson’s badge background used exactly that.
  • fillText(text, x, y) draws text, textAlign/textBaseline control how it’s positioned relative to (x, y).
  • drawImage(image, x, y, width, height) draws a loaded image onto the canvas, only after its onload event fires. save()/clip()/restore() restrict drawing to a shape, this lesson’s badge used it to make a square photo appear circular.
  • clearRect() erases a rectangular area, almost always called first when redrawing, this lesson’s drawBadge() cleared the whole canvas before every redraw so badges never stacked on top of each other.

Next lesson: this module’s exercises, practicing storage, cookies, and the API patterns from this module.