completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Drawing with canvas

Draw shapes, pixels, and animations with the canvas 2D context, including paths, ImageData, high-DPI sizing, animation loops, and OffscreenCanvas.

By the end, you can
  • 01
    Draw with the 2D contextSet the canvas size, colors, shapes, paths, and text, and stop drawings from looking blurry on sharp screens.
  • 02
    Work with pixelsRead and change pixel numbers with ImageData, and explain why a picture from another site can block reading pixels.
  • 03
    Animate with careClear and draw each frame with requestAnimationFrame(), respect people who want less motion, and check for OffscreenCanvas before using it.

Canvas is pixels, not page elements

A <canvas> is a rectangle on the page that you draw on with JavaScript. The rectangle is a grid of tiny colored dots called pixels. You can paint rectangles, lines, text, pictures, and color fades into it. You can also change single pixels.

Canvas is a good fit for things that redraw often:

  • games
  • paint and drawing apps
  • visual effects and music visualizers
  • busy charts with many points

The most important idea is this: a drawing call only changes pixels. When you call ctx.fillRect(20, 20, 80, 40), the browser colors a block of pixels. It does not keep a rectangle object that you can find later and move.

To move the rectangle, you keep its position in your own JavaScript data. Then you clear the canvas and draw everything again at the new position. This style is called immediate mode: each call paints right away and is then forgotten.

Left: ctx.fillRect(20, 20, 80, 40) paints pixels on a canvas, and no rectangle object is kept. Right: a div element on a page stays an element you can find and move.fillRect(20, 20, 80, 40)canvaspixelsno object kepta page elementpage<div>you can move it
fillRect() only colors pixels, so nothing is left to pick up later. A <div> on the page stays an element that you can find and move.
Real-life analogyCanvas is paint on a wall

Imagine you paint a circle on a wall. When the paint dries, there is no circle you can lift off the wall. Now imagine paper cutouts pinned to a board. Each cutout is its own thing, so you can unpin one and move it.

Canvas is the painted wall. Normal page elements are the paper cutouts.

In real life: Paint brushed onto a wall
In JavaScript: Canvas pixels after fillRect()
In real life: Paper cutouts pinned to a board
In JavaScript: Page elements (DOM or SVG) that the browser keeps
In real life: To move the paint, paint the wall again
In JavaScript: To move canvas content, clear and draw again
In real life: A notebook that lists the shapes you plan
In JavaScript: Your own JavaScript data about the shapes
The short definition

Canvas is a browser feature for drawing pixels with JavaScript. You draw through an object called the 2D context. You get it with canvas.getContext("2d"). Its full name is CanvasRenderingContext2D.

This lesson only covers the 2D context. WebGL and WebGPU are separate tools for fast graphics drawn by the graphics card, such as 3D scenes. They draw on the same <canvas> element, but you write very different code for them.

The 2D context and the drawing buffer

INTERACTIVE

You work with two objects:

  • The canvas element. It is the rectangle on the page.
  • The 2D context, usually saved in a variable called ctx. It is like a painter's toolbox. It has the drawing commands, and it remembers your settings.

The settings include the fill color (fillStyle), the outline color (strokeStyle), and the line thickness (lineWidth). The context also remembers moves and zooms (called transforms), a clipping area that limits where paint can land, and the current path. You'll meet paths in the next section.

Get the 2D contextPop out in the code editor (opens in a new tab)JavaScript
const canvas = document.querySelector("canvas");const ctx = canvas.getContext("2d"); ctx.fillStyle = "royalblue";ctx.fillRect(10, 10, 80, 40);ctx.clearRect(0, 0, canvas.width, canvas.height);

Line 1 finds the canvas element. Line 2 asks it for the 2D context. Line 4 sets the fill color to blue. Line 5 paints a blue rectangle: 10 pixels from the left, 10 from the top, 80 wide, and 40 tall. Line 6 wipes the whole canvas clear again.

A canvas has two sizes, and they are easy to mix up:

  • The width and height attributes set the drawing buffer: how many pixels the canvas really has. If you leave them out, the buffer is 300×150.
  • The CSS width and height only set how big the canvas looks on the page. CSS stretches the buffer to fit.

Many phones and laptops have sharp screens that pack 2 or 3 real screen dots into one CSS pixel. The browser tells you this number in devicePixelRatio. On those screens, a small stretched buffer looks blurry. The fix is to make the buffer bigger by that number, then scale the drawing back down to CSS pixels.

Make the buffer fit a sharp screenPop out in the code editor (opens in a new tab)JavaScript
function resizeCanvas(canvas, cssWidth, cssHeight, dpr) {  canvas.style.width = cssWidth + "px";  canvas.style.height = cssHeight + "px";  canvas.width = Math.round(cssWidth * dpr);  canvas.height = Math.round(cssHeight * dpr);  const ctx = canvas.getContext("2d");  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);  return ctx;} // With a device pixel ratio of 2: a 600×300 buffer.

Lines 2 and 3 set the size the canvas looks on the page. Lines 4 and 5 make the buffer bigger by the device pixel ratio. Line 7 scales the drawing, so your code can keep using CSS-sized numbers.

So with a device pixel ratio of 2, a canvas that is 300×150 on the page should have a 600×300 buffer. Your drawing code can still use numbers like 300 and 150, because setTransform(2, 0, 0, 2, 0, 0) doubles every number you draw with.

Save settings before a short changePop out in the code editor (opens in a new tab)JavaScript
ctx.fillStyle = "tomato";ctx.save();ctx.fillStyle = "royalblue";ctx.fillRect(10, 10, 30, 30);ctx.restore();ctx.fillRect(50, 10, 30, 30);

Line 1 sets the fill color to tomato red. Line 2, save(), puts all current settings aside. Line 3 changes the color to blue, and line 4 paints a blue square. Line 5, restore(), brings back the saved settings, so the color is tomato red again. Line 6 paints a red square.

save() keeps colors, line width, transforms, and the clipping area. restore() does not erase pixels you already painted. Put the pair around a short change, so the drawing after it doesn't get the changed setting by accident.

Start a fresh path for each shapePop out in the code editor (opens in a new tab)JavaScript
ctx.beginPath();ctx.moveTo(20, 30);ctx.lineTo(120, 30);ctx.stroke(); ctx.beginPath();ctx.moveTo(20, 70);ctx.lineTo(120, 70);ctx.stroke();

Lines 1 to 4 draw one line. Lines 6 to 9 draw a second line lower down. Each part starts with beginPath(). It throws away the old path that the context remembers. It does not erase any pixels.

If you skip beginPath() on line 6, the old line stays in the path. Then the second stroke() paints the first line again too.

Measure text before placing itPop out in the code editor (opens in a new tab)JavaScript
ctx.font = "16px sans-serif";const label = "Canvas";const width = ctx.measureText(label).width;ctx.fillText(label, 10, 30);console.log(Math.round(width) > 0);

Line 1 picks the font. Line 3 measures how wide the word will be, without drawing it. Line 4 paints the word at x = 10. The letters sit on an invisible line at y = 30, called the baseline. Line 5 prints true, because the width is more than 0.

Knowing the width first lets you center a label, or cut it short, before you paint it.

CSS size does not change the bufferPop out in the code editor (opens in a new tab)JavaScript
canvas.width = 300;canvas.height = 150;canvas.style.width = "600px";canvas.style.height = "300px";console.log(canvas.width + "x" + canvas.height);

Lines 1 and 2 set the real drawing buffer to 300×150. Lines 3 and 4 only make it look 600×300 on the page. So line 5 still prints 300x150. To get more pixels to draw on, change the attributes, not the CSS.

A small card, the 300 by 150 drawing buffer, set by lines 1 and 2. An arrow labelled CSS stretches it to a big card, 600 by 300 on the page, set by lines 3 and 4. Line 5 still logs 300x150.300×150drawing bufferlines 1 and 2CSS600×300shown on the pagelines 3 and 4line 5 logs 300x150
Lines 1 and 2 make a 300×150 buffer. Lines 3 and 4 only stretch it to 600×300 on the page, so line 5 still logs 300x150.
Shapes lab: paint pixels on a canvas
Shape commandsPop out in the code editor (opens in a new tab)JavaScript
const canvas = document.querySelector("canvas");const ctx = canvas.getContext("2d");ctx.fillStyle = "#5b8def";ctx.strokeStyle = "#1f2937";ctx.lineWidth = 4;ctx.fillRect(24, 24, 96, 56);ctx.strokeRect(148, 24, 96, 56);ctx.beginPath();ctx.arc(92, 142, 36, 0, Math.PI * 2);ctx.fill();ctx.beginPath();ctx.moveTo(170, 164);ctx.lineTo(214, 106);ctx.lineTo(258, 164);ctx.closePath();ctx.stroke();ctx.fillText("Canvas", 24, 210);
Canvas outputbigger buffer
Try it yourself

The drawing appears once the page has loaded, because only the browser can paint canvas pixels.

Turn each drawing command on or off, and switch between a sharp and a blurry buffer. The width and height attributes set the buffer; CSS only changes how big it looks.

Try the sharp/blurry button. The shapes stay the same; only the buffer size changes. You saw the same idea in Sizes, scrolling & coordinates: the browser usually measures in CSS pixels, but the screen may have more real dots than that.

Paths and shapes: sketch first, then ink

STEP THROUGH

Some canvas commands paint right away, like fillRect(). Paths work differently. A path is a sketch that the context keeps in memory. Nothing shows until you paint it. You build it step by step:

  • beginPath() starts a new, empty sketch.
  • moveTo(x, y) lifts the pencil and puts it at a point.
  • lineTo(x, y) adds a straight line. arc() adds a curve.
  • fill() paints the inside, or stroke() paints the outline. Only now do pixels change.
Real-life analogyA path is a pencil sketch before inking

A comic artist first draws a panel lightly in pencil. The pencil lines show where the ink will go. Readers only see the panel after the artist goes over it in ink.

In real life: Lift the pencil and move to a spot
In JavaScript: moveTo(x, y)
In real life: Sketch a line or curve lightly
In JavaScript: lineTo() and arc()
In real life: Join the end back to the start
In JavaScript: closePath()
In real life: Go over the sketch in ink
In JavaScript: stroke() or fill()
Lines 2 to 6 put a sketch into the path, drawn as a cart called path. Nothing shows yet. An arrow labelled fill() leads to a canvas window, where the sketch becomes pixels you can see.moveTo, lineTo, arcsketchpathnothing shows yetfill()canvaspixelsnow it shows
Lines 2 to 6 only build a sketch inside the context. Line 7, fill() or stroke(), paints it, and only then do pixels change.
Step through a path before it becomes pixels
Step 0 of 6Ready
Your turn: follow the blue line

A path is a sketch that the 2D context keeps. Step through the sketch, then paint it with fill or stroke.

Running in
  1. script
Next: line 2
const ctx = canvas.getContext("2d");ctx.moveTo(52, 124);ctx.lineTo(138, 42);ctx.arc(194, 124, 42, Math.PI, 0);ctx.closePath();ctx.fill();
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Change how the path ends

Each change starts a new recording. The preview draws the same path.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Live path previewclosed · fill

arc() measures angles in radians, not degrees. A full circle is Math.PI * 2. Half a circle is Math.PI. If you think in degrees, change them to radians first with degrees * Math.PI / 180.

Fill rules: nonzero and evenodd

A fill rule decides which areas count as "inside" a path. fill() uses the nonzero rule unless you say otherwise. Pass "evenodd" to cut a hole where one ring sits inside another, like a donut.

The rule only matters for filled paths whose parts overlap or sit inside each other. Plain rectangles and circles look the same with either rule.

Images and pixels: ImageData is a mosaic

INTERACTIVE

Canvas can draw pictures, frames from a video, color fades, and shapes made by code. Once pixels are on the canvas, getImageData() copies them into an ImageData object.

Its data property holds the pixels as one long list of numbers. Each pixel uses four numbers in a row: red, green, blue, and alpha. Alpha says how see-through the pixel is. Every number is from 0 to 255. The list type is Uint8ClampedArray: a list that can only hold whole numbers from 0 to 255.

Real-life analogyImageData is a mosaic of numbered tiles

Imagine a mosaic where every tile has four tiny knobs: red, green, blue, and alpha. A filter is a rule for turning the knobs on every tile.

In real life: One tiny tile
In JavaScript: One pixel
In real life: Four knobs on the tile
In JavaScript: Red, green, blue, alpha
In real life: Each knob turns from 0 to 255
In JavaScript: The numbers in Uint8ClampedArray
In real life: Put the mosaic back on the wall
In JavaScript: putImageData(image, 0, 0)

For example, to invert a picture (swap each color for its opposite), you set each color number to 255 - value. Alpha stays the same.

One pixel is four numbers: R 200, G 100, B 50, A 255. Arrows lead down to the inverted pixel: R 55, G 155, B 205, and A stays 255.red, green, blue: 255 - valuealpha: keptR 200G 100B 50A 255R 55G 155B 205A 255
One pixel is four numbers: red, green, blue, and alpha (how see-through it is). Inverting turns each color into 255 - value and keeps alpha as it was.
Pixels lab: change the numbers of every pixel
Pixel filter loopPop out in the code editor (opens in a new tab)JavaScript
const image = ctx.getImageData(0, 0, canvas.width, canvas.height);for (let i = 0; i < image.data.length; i += 4) {  const red = image.data[i];  const green = image.data[i + 1];  const blue = image.data[i + 2];  image.data[i] = 255 - red;  image.data[i + 1] = 255 - green;  image.data[i + 2] = 255 - blue;}ctx.putImageData(image, 0, 0);
Picture made by codeinvert
Try it yourself

Click a filter. It reads the pixels, changes their numbers, and puts them back. Move over the canvas to read the four numbers of one pixel.

This picture is drawn by code in this page. If you first drew a picture from another site without its permission, getImageData would throw a SecurityError instead.

The lab draws its own picture with code, so reading its pixels is allowed. Pictures from another website are different. An origin is the address of a website, like https://example.com. If you draw a picture from another origin without its permission, the canvas becomes tainted: marked as unsafe to read. After that, getImageData() throws a SecurityError.

This rule protects private pictures. For example, a page should not be able to read the pixels of a photo that only shows because the visitor is logged in to another site.

Draw an image only after it loadsJavaScript
const image = new Image();
image.onload = () => {
  ctx.drawImage(image, 0, 0);
  console.log("drawn after load");
};
image.src = "avatar.png";

Line 1 makes an empty image. Line 6 sets src, the picture's address, which starts the download. The pixels are not ready yet at that moment. Lines 2 to 5 set up onload, which runs when the download finishes. Only then does line 3 draw the picture with drawImage().

A picture from another origin also needs that site's permission before you can read its pixels. The site gives permission with a special response header, part of a system called CORS.

Reading pixels can be blockedJavaScript
const image = new Image();
image.crossOrigin = "anonymous";
image.onload = () => {
  ctx.drawImage(image, 0, 0);
  try {
    ctx.getImageData(0, 0, 1, 1);
  } catch (error) {
    console.log(error.name);
  }
};
image.src = "https://example.com/photo.png";

Line 2 asks for permission by setting crossOrigin. It must come before src on line 11. If the other site does not allow it, the picture won't load, so onload never runs. Without line 2, the picture would still draw, but the canvas would be tainted. Then line 6 would throw, and line 8 would print SecurityError.

A tainted canvas still shows on the page. Only reading its pixels from code is blocked. Your page cannot give itself permission; only the other site can.

An animation loop: clear and draw again

INTERACTIVE

A canvas animation is usually a loop. Each turn of the loop is one frame, and it does five things:

  1. Measure how much time has passed.
  2. Update your own data, like a ball's position.
  3. Clear the old pixels.
  4. Draw the new frame.
  5. Ask for the next frame with requestAnimationFrame().

requestAnimationFrame(frame) asks the browser to call frame just before it next paints the screen. You saw timing in Timers and the Event loop. Canvas adds the clear-and-draw-again step.

Animation loop: update, clear, draw, ask again
Bouncing ball loopPop out in the code editor (opens in a new tab)JavaScript
let x = 40;let velocity = 180;let lastTime = performance.now(); function frame(now) {  const seconds = (now - lastTime) / 1000;  lastTime = now;  x = x + velocity * seconds;  if (x > 260 || x < 40) velocity = -velocity;  ctx.clearRect(0, 0, 300, 160);  ctx.beginPath();  ctx.arc(x, 80, 22, 0, Math.PI * 2);  ctx.fill();  requestAnimationFrame(frame);}
Canvas animationpaused
Try it yourself

Motion only starts when you press Play. Press Step one frame or Play. The ball never starts moving on its own.

Each frame clears the old pixels before drawing the ball again, so no trail is left. requestAnimationFrame asks for the next frame.

The lab does not start moving on its own. Some people feel unwell when things move on screen, so their computer asks pages to use less motion. A page can check this with the prefers-reduced-motion setting.

The lab checks that setting after the page loads. It gives you a Step button to move one frame at a time. It also cancels its next frame when the lab leaves the page.

OffscreenCanvas: draw in a worker

FEATURE DETECT

Normally, all your page code runs on one track, called the main thread. Clicks, typing, and scrolling are handled there too. A worker is a second track that runs code at the same time.

OffscreenCanvas lets a worker do the drawing. That helps when drawing is heavy enough to slow down clicks, typing, or scrolling. Most new browsers support it, but not all. So you first check that it exists. This check is called feature detection.

OffscreenCanvas: draw from a worker if the browser can
Hand the canvas to a worker, if possiblePop out in the code editor (opens in a new tab)JavaScript
if ("OffscreenCanvas" in window) {  const offscreen = canvas.transferControlToOffscreen();  const worker = new Worker(workerUrl);  worker.postMessage({ canvas: offscreen }, [offscreen]);} else {  showMessage("This browser does not support OffscreenCanvas yet.");}
Worker canvaschecked first
Try it yourself

Press Try worker drawing to check whether this browser has OffscreenCanvas.

Not every browser has OffscreenCanvas. Check for it first, and always keep a way to draw without it.

Line 1 checks that the browser has OffscreenCanvas. Line 2 hands control of the canvas over to an OffscreenCanvas. Line 3 starts a worker. Line 4 sends the canvas to the worker. Line 6 shows a message when the browser has no support.

The demo only starts a worker when you click. It builds the worker's code from a Blob URL, a short-lived address for text made in the page. Reset stops the worker with terminate() and frees the Blob URL.

This cleanup matters, because a worker keeps running until you stop it. Also, once a canvas is handed to a worker, the main page can no longer draw on it.

Choose canvas, SVG, or normal page elements

SORTER

Canvas is powerful, but it does not replace HTML. People need to click into things, select text, search the page, copy, and use screen readers (tools that read the page out loud). For that, normal page elements, the DOM, usually work best.

SVG sits in between. It draws shapes like canvas, but the browser keeps each shape as an element.

Give canvas a useful fallbackHTML
<canvas width="300" height="150" aria-label="Sales by drink">
  <table>
    <caption>Sales by drink</caption>
    <tr><th>Tea</th><td>3</td></tr>
    <tr><th>Bread</th><td>5</td></tr>
  </table>
</canvas>

Line 1 gives the canvas a name with aria-label. Screen readers say that name. Lines 2 to 6 put a real table inside the canvas. People can read the table if the canvas can't be drawn.

For charts, it is best to also show the data in a real table next to the picture. Then the chart is not the only way to read the numbers.

The table compares the three tools. Read each row as one question, and each column as one tool's answer.

Canvas, SVG, and page elements side by side
QuestionCanvasSVGDOM (page elements)
What does the browser keep?Only the pixels in the drawing buffer.One element for each shape.One element for each part of the page.
What is it best at?Games, paint apps, and busy charts.Diagrams and charts made of real shapes.Forms, text, and page layout.
How do you find what was clicked?You compare the click with your own data, using math.The browser tells you which shape was clicked.The browser tells you which element was clicked.
How do screen readers understand it?You add a separate label or real controls.Each shape can have its own label.Most elements, like buttons and headings, work as they are.
Which drawing tool fits?
  • A brush app with thousands of strokes
  • A line chart where each point needs a tooltip and keyboard focus
  • A checkout form with inputs, labels, and validation messages
  • A fast game with moving characters, drawn 60 times a second
  • A logo that should stay sharp at any size
  • A paragraph with links that people can select and search
Try it yourself
0 of 6 correct

Sort each real task into canvas, SVG, or DOM.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Where you'll use this in real projects

A real canvas feature keeps plain JavaScript data next to the pixels. For example:

  • A diagram keeps an array of boxes and the lines between them.
  • A game keeps each character's position and speed.
  • A signature pad keeps the list of pen strokes.

The data is the real record of what is on screen. The canvas only shows a picture of it.

Draw a small bar chartPop out in the code editor (opens in a new tab)JavaScript
const sales = [3, 5, 2];const labels = ["Tea", "Bread", "Rice"];const ctx = canvas.getContext("2d");ctx.fillStyle = "royalblue";sales.forEach((amount, index) => {  const x = 20 + index * 70;  const y = 140 - amount * 20;  ctx.fillRect(x, y, 42, amount * 20);  ctx.fillStyle = "black";  ctx.fillText(labels[index], x, 158);  ctx.fillStyle = "royalblue";});

Line 1 holds three numbers. Line 5 loops over them. Each time, line 6 picks the bar's left edge, and line 7 picks its top edge. Line 8 paints the bar. Lines 9 and 10 write its name underneath in black.

Why does line 7 subtract? On a canvas, y grows downward: 0 is the top. To make a bar stand on a base line at y = 140, its top must be higher up, which means a smaller y.

Keep the shapes in an arrayPop out in the code editor (opens in a new tab)JavaScript
const circles = [  { x: 40, y: 50, radius: 18, color: "tomato" },  { x: 120, y: 80, radius: 28, color: "royalblue" },]; function drawScene(ctx) {  ctx.clearRect(0, 0, 300, 150);  for (const circle of circles) {    ctx.fillStyle = circle.color;    ctx.beginPath();    ctx.arc(circle.x, circle.y, circle.radius, 0, Math.PI * 2);    ctx.fill();  }}

Line 1 keeps the two circles as plain objects. Line 7 clears the canvas. Line 8 loops over the circles, and line 11 draws each one. To move a circle, change its x in the array and call drawScene() again.

Finding which shape was clicked is called hit testing. It uses the same array. Canvas can't tell you which circle was clicked. You compare the click's position with your saved shapes instead:

  • For a rectangle, check that the point is between its left and right edges, and between its top and bottom.
  • For a circle, check that the point is no farther from the center than the radius.

Common misconceptions

  • "Canvas shapes are page elements." They are only pixels. Keep your own data if you need to move, delete, label, or click on a shape.
  • "The CSS size is the canvas size." The width and height attributes set the drawing buffer. CSS only stretches it.
  • "lineTo() draws a line right away." Path commands only sketch. stroke() or fill() paints.
  • "Pixel filters can read any picture." A picture from another site without permission taints the canvas. Then getImageData() throws a SecurityError.
  • "Canvas animation never clears." Most loops clear and draw again every frame. A trail behind a moving thing only appears if you choose to skip the clear.
  • "OffscreenCanvas makes everything fast." It can move drawing to a worker. You still pay for sending messages, copying data, a fallback, and cleanup.

Practice exercises

5 EXERCISES
Exercise 1 · Warm-upPredict the radians conversion

Before you run it, predict what the console prints.

Starter codePop out in the code editor (opens in a new tab)JavaScript
const degrees = 180;
const radians = degrees * Math.PI / 180;
console.log(radians === Math.PI);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticePick the buffer size for a sharp screen

    Work out the buffer size for a canvas that is 300×150 on the page, on a screen with a device pixel ratio of 2.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    function backing(cssWidth, cssHeight, dpr) {
      return [cssWidth * dpr, cssHeight * dpr].join("x");
    }
    console.log(backing(300, 150, 2));

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeInvert one pixel

      Predict the four numbers it prints, separated by commas.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const pixel = [10, 20, 30, 255];
      console.log([255 - pixel[0], 255 - pixel[1], 255 - pixel[2], pixel[3]].join(","));

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeFind the bug: the path never shows

        The code runs without an error, but the canvas stays blank. Explain which line is missing, and add it.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        ctx.beginPath();
        ctx.moveTo(20, 20);
        ctx.lineTo(120, 20);
        ctx.lineTo(120, 80);
        // Why is nothing visible?
          Exercise 5 · ChallengeFind the clicked button

          Two buttons are drawn on a canvas. Write hitButton() so it returns the button under a click point. Assume the point is already in canvas pixels.

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          const buttons = [
            { x: 20, y: 20, width: 90, height: 36, label: "Save" },
            { x: 120, y: 20, width: 90, height: 36, label: "Cancel" },
          ];
          
          function hitButton(point) {
            // return the clicked button, or undefined
          }

            Check your understanding

            7 QUESTIONS
            Canvas quiz · 7 questionsScore: first tries count
            1. Question 1 of 7A canvas has no width or height attributes. How big is its drawing buffer?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does this angle code print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log(Math.PI === 180 * Math.PI / 180);

              Choose an answer to see the explanation.

            3. Question 3 of 7Which sentence about paths is true?

              Choose an answer to see the explanation.

            4. Question 4 of 7What kind of list is ImageData.data?

              Choose an answer to see the explanation.

            5. Question 5 of 7What can make getImageData throw a SecurityError?

              Choose an answer to see the explanation.

            6. Question 6 of 7What does a usual canvas animation loop do each frame?

              Choose an answer to see the explanation.

            7. Question 7 of 7What is the safest way to use OffscreenCanvas?

              Choose an answer to see the explanation.

            Key takeaways

            • Canvas drawing calls only change pixels. They don't make page elements you can move later.
            • The 2D context holds the drawing commands, your settings, and the current path. save() and restore() put settings aside and bring them back.
            • The width and height attributes set the drawing buffer. CSS only stretches it, so sharp screens need a bigger buffer.
            • A path is a sketch until fill() or stroke() paints it. Angles are in radians.
            • ImageData.data holds four numbers per pixel: red, green, blue, and alpha. You can't read the pixels of a tainted canvas.
            • Animations clear and draw again inside requestAnimationFrame(). Check that OffscreenCanvas exists before you use it.

            Canvas is pixel drawing with JavaScript: keep your own data about the shapes, and paint each frame from that data.

            Up next: Files & Blobs.

            CompleteFrontend Clear concepts. Working examples.