# HTML Canvas API Tutorial

> Learn the HTML Canvas API by drawing shapes, paths, text, and images, then handle high-DPI sizing, accessibility, and animation.

Author: [Flavio Copes](https://flaviocopes.com/about/) | Published: 2018-11-04 | Updated: 2026-07-18 | Topics: [JavaScript](https://flaviocopes.com/tags/js/) | Canonical: https://flaviocopes.com/canvas/

The HTML `<canvas>` element gives JavaScript a bitmap drawing surface. You can use it for charts, games, image editing, data visualizations, and other graphics that change at runtime.

Canvas is an immediate-mode API: drawing commands change pixels. The browser does not keep each rectangle, line, or label as a DOM element that you can edit later.

## Create a canvas

Give the element explicit `width` and `height` attributes:

```html
<canvas id="canvas" width="600" height="300">
  A chart showing monthly sales.
</canvas>
```

The content between the tags is fallback content. It is shown when canvas is unsupported and can give assistive technology a useful alternative.

Without explicit attributes, the canvas drawing buffer defaults to 300 by 150 pixels.

You can style the element with CSS:

```css
canvas {
  border: 1px solid currentColor;
  max-width: 100%;
}
```

Do not use CSS alone to define the drawing buffer size. Stretching a 300-by-150 buffer to different CSS dimensions can blur or distort the result.

## Get the 2D rendering context

Call `getContext('2d')`:

```js
const canvas = document.querySelector('#canvas')
const context = canvas.getContext('2d')

if (!context) {
  throw new Error('The 2D canvas context is not available')
}
```

The context contains the drawing methods and state.

`getContext()` can also request contexts such as `webgl`, `webgl2`, `webgpu`, and `bitmaprenderer`. Support varies, and the method returns `null` when the requested context is unavailable.

## Understand canvas coordinates

The origin is the top-left corner:

- `x` increases from left to right
- `y` increases from top to bottom

The point `(0, 0)` is at the top left. The point `(600, 300)` is at the bottom right of a 600-by-300 drawing buffer.

## Draw rectangles

`fillRect()` draws a filled rectangle:

```js
context.fillStyle = '#2563eb'
context.fillRect(20, 20, 160, 90)
```

The four arguments are `x`, `y`, `width`, and `height`.

`strokeRect()` draws only the outline:

```js
context.strokeStyle = '#111827'
context.lineWidth = 4
context.strokeRect(210, 20, 160, 90)
```

`fillStyle` and `strokeStyle` are properties, not methods. They remain active until you change them or restore an earlier context state.

Clear an area with `clearRect()`:

```js
context.clearRect(0, 0, canvas.width, canvas.height)
```

This makes the pixels transparent. It does not draw the current background color.

## Draw paths

A path can contain lines, curves, arcs, and multiple subpaths.

Start a new path with `beginPath()`, describe it, and then fill or stroke it:

```js
context.beginPath()
context.moveTo(60, 220)
context.lineTo(140, 140)
context.lineTo(220, 220)
context.closePath()

context.fillStyle = '#f59e0b'
context.fill()

context.strokeStyle = '#78350f'
context.lineWidth = 3
context.stroke()
```

Call `beginPath()` before an unrelated shape. Otherwise, later calls to `stroke()` or `fill()` can affect earlier subpaths too.

## Draw circles and arcs

Use `arc()`:

```js
context.beginPath()
context.arc(
  320,        // center x
  190,        // center y
  50,         // radius
  0,          // start angle
  Math.PI * 2 // end angle
)
context.fillStyle = '#10b981'
context.fill()
```

Angles are measured in radians. `Math.PI * 2` is one complete circle.

## Save and restore drawing state

The context stores styles, transforms, clipping regions, and other settings.

Use `save()` before a temporary change and `restore()` afterward:

```js
context.save()

context.translate(450, 160)
context.rotate(Math.PI / 8)
context.fillStyle = '#ef4444'
context.fillRect(-50, -25, 100, 50)

context.restore()
```

`restore()` returns to the most recently saved state. It does not undo pixels that were already drawn.

## Draw text

Set a font and call `fillText()`:

```js
context.font = '700 28px system-ui'
context.fillStyle = '#111827'
context.textAlign = 'center'
context.textBaseline = 'middle'

context.fillText('Hello canvas', 300, 150)
```

The `x` coordinate is interpreted according to `textAlign`. The `y` coordinate marks the text baseline according to `textBaseline`; it is not automatically the bottom-left corner of the text.

Measure text before laying it out:

```js
const metrics = context.measureText('Hello canvas')
console.log(metrics.width)
```

Canvas text is pixels, not selectable DOM text. Keep important page content in HTML and use canvas as a visual enhancement.

## Draw an image

Wait for an image to decode before drawing it:

```js
const image = new Image()
image.src = '/images/photo.jpg'

await image.decode()

context.drawImage(image, 20, 20, 240, 160)
```

The last four arguments place and scale the image.

You can also draw another canvas, an `ImageBitmap`, or a video frame.

Cross-origin images need the correct CORS headers if you later call methods such as `toDataURL()`, `toBlob()`, or `getImageData()`. Otherwise, the browser marks the canvas as not origin-clean and blocks access to its pixels.

## Handle high-DPI screens

On a high-density display, one CSS pixel can cover several device pixels. A canvas sized only for CSS pixels may look blurry.

This helper sizes the drawing buffer for the device pixel ratio:

```js
function resizeCanvas(canvas, context, cssWidth, cssHeight) {
  const ratio = window.devicePixelRatio || 1

  canvas.style.width = `${cssWidth}px`
  canvas.style.height = `${cssHeight}px`
  canvas.width = Math.round(cssWidth * ratio)
  canvas.height = Math.round(cssHeight * ratio)

  context.scale(ratio, ratio)
}

resizeCanvas(canvas, context, 600, 300)
```

After this runs, draw using the CSS-pixel coordinate system: a width of `600` still means the right edge.

Setting `canvas.width` or `canvas.height` clears the bitmap and resets the context state, even when you assign the same value. Resize first, then set styles, transforms, and draw the scene again.

For a responsive canvas, observe its rendered size with `ResizeObserver`, update the buffer when it changes, and redraw.

## Animate with requestAnimationFrame

Clear and redraw the scene for each frame:

```js
let x = 0
let previousTime = 0

function draw(time) {
  const seconds = (time - previousTime) / 1000
  previousTime = time

  x = (x + 120 * seconds) % canvas.width

  context.clearRect(0, 0, canvas.width, canvas.height)
  context.fillStyle = '#2563eb'
  context.fillRect(x, 120, 40, 40)

  requestAnimationFrame(draw)
}

requestAnimationFrame(draw)
```

`requestAnimationFrame()` lets the browser schedule drawing with the display refresh. Use the timestamp to make motion independent of the frame rate.

When a canvas uses a scaled high-DPI coordinate system, keep the logical CSS width separately instead of using `canvas.width` in movement calculations.

## Canvas accessibility

Pixels do not create semantic controls. If users need to interact with objects drawn on the canvas, provide an equivalent keyboard-accessible interface in HTML.

Also provide a text alternative for meaningful visual information. Depending on the content, that might be fallback text, a nearby description, a data table, or an `aria-label`.

Canvas is a good choice for dynamic bitmap graphics. For interactive document content made of separate elements, regular HTML or SVG can be easier to style, inspect, and make accessible.

See MDN for [basic canvas usage](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_usage), [drawing shapes](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes), [drawing text](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_text), and [canvas optimization](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Optimizing_canvas).
