The canvas Element
A blank bitmap you draw on with JavaScript. Powerful for graphics and games, and invisible to every reader who cannot see the screen.
-
HTML Basics
- What is HTML: The Structure Layer of Every Web Page
- HTML Document Structure: DOCTYPE, html, head and body
- Elements, Tags and Attributes: The Vocabulary of HTML
- HTML Comments: Notes That Ship With Your Code
- Block Level and Inline Elements
- Writing and Running Your First HTML Page
- How a Browser Turns Markup Into a Page
- Text and Formatting
- Links and Navigation
- Images and Media
- Lists
- Tables
-
Forms
- Form Structure: form, action and method
- Input Types: Text, Email, Number, Date and the Rest
- Labels: The Most Important Element in a Form
- Checkboxes, Radio Buttons and Grouping
- select, option, optgroup and datalist
- textarea, File Uploads and Hidden Fields
- Buttons: submit, reset and button
- Built In Form Validation
- GET or POST: What Happens When a Form Is Submitted
- Semantic HTML
- HTML5 Features
- Head and Metadata
- HTML with CSS
- HTML with JavaScript
- Accessibility
-
HTML SEO
- How Google Works: Crawling, Indexing and Ranking
- SEO Friendly HTML Structure
- Titles and Descriptions That Earn Clicks
- Headings and Content Structure for Search
- Internal Linking and Anchor Text
- robots.txt and XML Sitemaps
- Canonical URLs and Duplicate Content
- Structured Data and JSON-LD
- Image SEO
- Core Web Vitals and Mobile Friendliness
- DevTools and Debugging
- Editor Productivity
- HTML Best Practices
- HTML Projects
- Advanced Projects
- Practice and Exams
Concept
canvas is a rectangle of pixels that JavaScript draws into. The markup declares the surface and nothing else - every line, shape and character is drawn by script through a drawing context.
Because the result is a bitmap, nothing inside a canvas exists in the DOM. There are no elements to select, no text to search, and nothing for a screen reader to read. That constraint decides where canvas belongs.
Syntax
<canvas id="chart" width="600" height="400">
A bar chart of bus ridership from 2023 to 2026, rising from 1.2 to 2.1 million.
</canvas>The content between the tags is fallback, shown when canvas is unavailable. It is also the only text available to assistive technology, so describe what the canvas shows rather than writing your browser does not support canvas.
The width and height trap
The width and height attributes set the drawing surface in pixels. CSS width and height set the display size. They are different things, and mismatching them stretches the bitmap.
<!-- 300 by 150 pixels of bitmap stretched across 600 by 400: blurry -->
<canvas style="width:600px; height:400px"></canvas>
<!-- correct -->
<canvas width="600" height="400"></canvas>The default surface is 300 by 150, which is why an unstyled canvas is that size. On a high density screen the surface should be scaled up and then shrunk with CSS:
const canvas = document.getElementById("chart");
const ratio = window.devicePixelRatio || 1;
canvas.width = 600 * ratio;
canvas.height = 400 * ratio;
canvas.style.width = "600px";
canvas.style.height = "400px";
const ctx = canvas.getContext("2d");
ctx.scale(ratio, ratio);Drawing
const ctx = document.getElementById("chart").getContext("2d");
// filled rectangle
ctx.fillStyle = "#1e3a8a";
ctx.fillRect(20, 20, 120, 80);
// outlined rectangle
ctx.strokeStyle = "#dc2626";
ctx.lineWidth = 3;
ctx.strokeRect(160, 20, 120, 80);
// a path
ctx.beginPath();
ctx.moveTo(20, 140);
ctx.lineTo(120, 220);
ctx.lineTo(220, 140);
ctx.closePath();
ctx.stroke();
// a circle
ctx.beginPath();
ctx.arc(320, 180, 40, 0, Math.PI * 2);
ctx.fillStyle = "#16a34a";
ctx.fill();
// text
ctx.font = "16px system-ui, sans-serif";
ctx.fillStyle = "#0f172a";
ctx.fillText("Ridership by year", 20, 280);
// an image
const img = new Image();
img.onload = () => ctx.drawImage(img, 20, 300, 160, 90);
img.src = "/images/logo.png";
// clear everything
ctx.clearRect(0, 0, canvas.width, canvas.height);The coordinate origin is the top left corner, x increases to the right and y increases downwards.
Animation
let x = 0;
function frame() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#1e3a8a";
ctx.fillRect(x, 100, 40, 40);
x = (x + 2) % canvas.width;
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);Use requestAnimationFrame rather than a timer. It matches the display refresh rate and pauses when the tab is hidden, which saves battery.
canvas or SVG?
| canvas | SVG | |
|---|---|---|
| Model | Pixels | Elements in the DOM |
| Scales without loss | No | Yes |
| Selectable text | No | Yes |
| Accessible | Only through fallback text | Yes, with title and desc |
| Events on shapes | Hit testing by hand | Built in |
| Many thousands of objects | Fast | Slows down |
| Styled with CSS | No | Yes |
The rule: SVG for anything a reader might need to read, select or interact with. Canvas for large numbers of moving pixels. A chart on a content page should be SVG. A particle simulation or a game should be canvas.
Accessibility
A canvas is opaque to assistive technology. Anything meaningful drawn in it must be provided again in text.
<figure>
<canvas id="chart" width="600" height="400" role="img"
aria-label="Bus ridership by year, rising from 1.2 million in 2023 to 2.1 million in 2026">
</canvas>
<figcaption>Bus ridership, 2023 to 2026</figcaption>
</figure>
<table class="visually-hidden">
<caption>Bus ridership by year</caption>
<tr><th scope="col">Year</th><th scope="col">Passengers</th></tr>
<tr><td>2023</td><td>1,200,000</td></tr>
<tr><td>2026</td><td>2,100,000</td></tr>
</table>The hidden table is the honest solution: the data is available to any reader, in a form they can navigate.
Important rules
widthandheightattributes set the surface, CSS sets the display size.- Setting either attribute clears the canvas entirely.
- Nothing drawn in a canvas is in the DOM.
- Drawing an image from another origin taints the canvas, and
toDataURLthen throws. - Text in canvas cannot be selected, searched, translated or zoomed by the browser.
getContext("2d")for two dimensional drawing; WebGL contexts are a separate topic.
Common mistakes
- Sizing with CSS only and getting a stretched 300 by 150 bitmap.
- Ignoring device pixel ratio and shipping blurry output on modern screens.
- Resetting the width in a resize handler and wiping the drawing.
- Using canvas for a chart on a content page, making the data unreadable.
- Providing no fallback text.
- Animating with
setIntervalinstead ofrequestAnimationFrame.
Best practices
- Set the surface size with attributes and the display size with CSS.
- Scale for device pixel ratio.
- Provide the same information in accessible text or a hidden table.
- Choose SVG unless you have a specific reason not to.
- Redraw on resize, and remember the canvas is cleared when you do.
- Respect
prefers-reduced-motionfor animation.
Practice
- Draw a rectangle, a circle and a line, then style the canvas with CSS only and observe the distortion.
- Scale a canvas for device pixel ratio and compare the text sharpness before and after.
- Build a simple bar chart in canvas, then rebuild it in SVG. Compare accessibility.
- Animate a shape with
requestAnimationFrameand confirm it pauses when the tab is hidden.