HTML Canvas API Tutorial
By Flavio Copes
Learn the HTML Canvas API by drawing shapes, paths, text, and images, then handle high-DPI sizing, accessibility, and animation.
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:
<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:
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'):
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:
xincreases from left to rightyincreases 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:
context.fillStyle = '#2563eb'
context.fillRect(20, 20, 160, 90)
The four arguments are x, y, width, and height.
strokeRect() draws only the outline:
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():
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:
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():
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:
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():
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:
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:
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:
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:
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, drawing shapes, drawing text, and canvas optimization.
Related posts about js: