The canvas Element
The <canvas> element creates a rectangular drawing area on the page. By itself it displays nothing β you draw on it with JavaScript.
<!-- width and height are the actual pixel dimensions of the drawing surface -->
<canvas id="myCanvas" width="600" height="400">
<!-- Fallback content for browsers that don't support canvas (very rare today) -->
Your browser does not support the HTML canvas element.
</canvas>
width and height as HTML attributes, not CSS. CSS can scale the canvas element visually, but the drawing buffer size is controlled by the HTML attributes. If you size the canvas only with CSS, drawings will appear blurry because the buffer stays at the default 300Γ150 pixels.
Getting the 2D Context
Before drawing anything, you must get the CanvasRenderingContext2D object by calling getContext('2d'). All drawing methods live on this context object.
<canvas id="myCanvas" width="600" height="400"></canvas>
<script>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// ctx is now the 2D drawing context β all drawing methods are called on ctx
// canvas refers to the DOM element (width, height properties, etc.)
</script>
Drawing Rectangles
Rectangles are the only shape with dedicated shorthand methods. All three use the same coordinate system: (x, y, width, height) where (0,0) is the top-left corner.
<canvas id="rectCanvas" width="400" height="200"></canvas>
<script>
const ctx = document.getElementById('rectCanvas').getContext('2d');
// fillRect(x, y, width, height) β filled rectangle
ctx.fillStyle = '#e67e22';
ctx.fillRect(20, 20, 150, 100);
// strokeRect(x, y, width, height) β outlined rectangle (no fill)
ctx.strokeStyle = '#2c3e50';
ctx.lineWidth = 3;
ctx.strokeRect(200, 20, 150, 100);
// clearRect(x, y, width, height) β erases pixels (makes them transparent)
ctx.fillStyle = '#3498db';
ctx.fillRect(180, 60, 100, 100);
ctx.clearRect(200, 80, 60, 60); // cuts a hole in the blue rect
</script>
Drawing Paths β Lines, Arcs, Circles
For any shape that is not a rectangle, you use paths. A path is a sequence of points connected by lines and curves. The workflow is always: beginPath() β define the path β stroke() or fill().
<canvas id="pathCanvas" width="400" height="300"></canvas>
<script>
const ctx = document.getElementById('pathCanvas').getContext('2d');
// --- Triangle using lines ---
ctx.beginPath();
ctx.moveTo(200, 20); // lift pen and move to starting point
ctx.lineTo(350, 180); // draw line to this point
ctx.lineTo(50, 180); // draw line to this point
ctx.closePath(); // draws a line back to the starting point
ctx.fillStyle = '#9b59b6';
ctx.fill();
ctx.strokeStyle = '#2c3e50';
ctx.lineWidth = 2;
ctx.stroke();
// --- Circle using arc() ---
// arc(x, y, radius, startAngle, endAngle, anticlockwise)
// Angles in radians: full circle = 2 * Math.PI
ctx.beginPath();
ctx.arc(200, 240, 40, 0, 2 * Math.PI);
ctx.fillStyle = '#e74c3c';
ctx.fill();
// --- Semicircle ---
ctx.beginPath();
ctx.arc(80, 240, 30, 0, Math.PI); // 0 to PI = bottom semicircle
ctx.fillStyle = '#27ae60';
ctx.fill();
// --- Pie slice (arc + lines to center) ---
ctx.beginPath();
ctx.moveTo(330, 240);
ctx.arc(330, 240, 40, 0, Math.PI * 0.75);
ctx.closePath();
ctx.fillStyle = '#f39c12';
ctx.fill();
</script>
Drawing Text
Canvas provides fillText() for solid text and strokeText() for outlined text. The font property uses the same syntax as CSS font.
<canvas id="textCanvas" width="400" height="200"></canvas>
<script>
const ctx = document.getElementById('textCanvas').getContext('2d');
// Font syntax: "style weight size family"
ctx.font = 'bold 48px sans-serif';
ctx.fillStyle = '#2c3e50';
ctx.fillText('Hello Canvas!', 20, 70);
// Outlined text
ctx.font = '36px Georgia, serif';
ctx.strokeStyle = '#e74c3c';
ctx.lineWidth = 1;
ctx.strokeText('Outlined', 20, 130);
// Text alignment
ctx.font = '20px Arial';
ctx.textAlign = 'center'; // 'left' | 'center' | 'right'
ctx.textBaseline = 'middle'; // 'top' | 'middle' | 'alphabetic' | 'bottom'
ctx.fillStyle = '#27ae60';
ctx.fillText('Centered text', 200, 170);
// Measure text width before drawing
const metrics = ctx.measureText('Hello Canvas!');
console.log('Text width:', metrics.width); // useful for layout
</script>
Drawing Images
Use drawImage() to render <img> elements, other canvases, or video frames onto the canvas.
<canvas id="imgCanvas" width="400" height="300"></canvas>
<script>
const canvas = document.getElementById('imgCanvas');
const ctx = canvas.getContext('2d');
const img = new Image();
img.src = '/assets/sample.jpg';
// Must wait for image to load before drawing
img.onload = function() {
// drawImage(image, dx, dy) β draw at position
ctx.drawImage(img, 0, 0);
// drawImage(image, dx, dy, dWidth, dHeight) β draw scaled
ctx.drawImage(img, 200, 0, 200, 150);
// drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
// sx/sy/sWidth/sHeight = crop from source
// dx/dy/dWidth/dHeight = destination on canvas
ctx.drawImage(img, 50, 50, 100, 100, 0, 150, 200, 150);
};
</script>
Colors and Styles
<canvas id="styleCanvas" width="400" height="200"></canvas>
<script>
const ctx = document.getElementById('styleCanvas').getContext('2d');
// fillStyle and strokeStyle accept any CSS color value
ctx.fillStyle = '#e74c3c'; // hex
ctx.fillStyle = 'rgb(231,76,60)'; // rgb
ctx.fillStyle = 'rgba(231,76,60,0.5)'; // rgba (semi-transparent)
ctx.fillStyle = 'hsl(6, 78%, 57%)'; // hsl
// Linear gradient
const linearGrad = ctx.createLinearGradient(0, 0, 400, 0); // x0,y0 β x1,y1
linearGrad.addColorStop(0, '#3498db');
linearGrad.addColorStop(1, '#9b59b6');
ctx.fillStyle = linearGrad;
ctx.fillRect(0, 0, 400, 80);
// Radial gradient
const radialGrad = ctx.createRadialGradient(200, 150, 10, 200, 150, 70);
radialGrad.addColorStop(0, '#f1c40f');
radialGrad.addColorStop(1, '#e67e22');
ctx.fillStyle = radialGrad;
ctx.beginPath();
ctx.arc(200, 150, 70, 0, 2 * Math.PI);
ctx.fill();
// Line styles
ctx.strokeStyle = '#2c3e50';
ctx.lineWidth = 5;
ctx.lineCap = 'round'; // 'butt' | 'round' | 'square'
ctx.lineJoin = 'round'; // 'miter' | 'round' | 'bevel'
ctx.setLineDash([10, 5]); // dashed line: 10px dash, 5px gap
</script>
Simple Animation with requestAnimationFrame
requestAnimationFrame() tells the browser to call your drawing function before the next screen repaint β typically 60 times per second. The pattern is: clear the canvas, update positions, draw, then schedule the next frame.
<canvas id="animCanvas" width="500" height="300"></canvas>
<script>
const canvas = document.getElementById('animCanvas');
const ctx = canvas.getContext('2d');
// Ball state
let x = 250, y = 150;
let dx = 3, dy = 2; // velocity (pixels per frame)
const radius = 20;
function draw() {
// 1. Clear the entire canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 2. Draw background
ctx.fillStyle = '#ecf0f1';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 3. Draw the ball
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2 * Math.PI);
ctx.fillStyle = '#e74c3c';
ctx.fill();
ctx.strokeStyle = '#c0392b';
ctx.lineWidth = 2;
ctx.stroke();
// 4. Update position
x += dx;
y += dy;
// 5. Bounce off walls
if (x + radius > canvas.width || x - radius < 0) dx = -dx;
if (y + radius > canvas.height || y - radius < 0) dy = -dy;
// 6. Request next frame
requestAnimationFrame(draw);
}
// Start the animation loop
requestAnimationFrame(draw);
</script>
When to Use Canvas vs SVG
| Factor | Canvas | SVG |
|---|---|---|
| Rendering model | Immediate (pixel bitmap) | Retained (DOM tree of shapes) |
| Performance at scale | Better for thousands of objects | Slows with many DOM nodes |
| Resolution | Fixed pixel size (blurry when scaled) | Infinitely scalable (vector) |
| Interactivity | Manual hit-testing required | CSS/JS events work naturally |
| Accessibility | Needs ARIA fallback or alt text | Text inside SVG is readable |
| Animation | Excellent (60fps games, physics) | Good for simple transitions |
| Best for | Games, image manipulation, charts with many data points | Icons, logos, charts, maps, UI graphics |
π Summary
- Set canvas
widthandheightas HTML attributes β CSS sizing scales visually but does not change the pixel buffer. - Get the 2D context with
canvas.getContext('2d')β all drawing methods are on the returnedctxobject. - Rectangle shortcuts:
fillRect,strokeRect,clearRect. - For all other shapes use the path API:
beginPath()βmoveTo(),lineTo(),arc()βfill()/stroke(). - Draw text with
fillText()/strokeText(); setctx.fontbefore drawing. - Draw images with
drawImage()inside the image'sonloadhandler. - Animate with
requestAnimationFrame(): clear, update, draw, repeat. - Choose Canvas for pixel-heavy animations and games; choose SVG for scalable graphics and interactive diagrams.
FAQ
Why does my canvas drawing look blurry on Retina/HiDPI screens?
On HiDPI screens, the device pixel ratio (DPR) is typically 2 or 3. The canvas buffer is drawn at its HTML attribute size but displayed at double the physical pixels, causing blurriness. Fix: multiply the canvas width/height by window.devicePixelRatio, set CSS width/height to the original size, and scale the context with ctx.scale(dpr, dpr) before drawing.
How do I stop a requestAnimationFrame animation?
Store the ID returned by requestAnimationFrame(draw) in a variable, then call cancelAnimationFrame(id) when you want to stop. Example: let animId = requestAnimationFrame(draw); then later cancelAnimationFrame(animId);. A common pattern is to set a boolean flag like running = false that the draw function checks before scheduling the next frame.
Can I add text that is selectable and accessible inside a canvas?
No. Canvas text is rasterised as pixels β it cannot be selected, copied, translated, or read by screen readers. For accessible interactive graphics, consider SVG instead. If you must use canvas, provide an equivalent text description in the surrounding HTML or as a aria-label on the canvas element itself: <canvas aria-label="Bar chart: sales increased 40% in Q2 2026"></canvas>.