JavaScript Canvas: Drawing Shapes, Images, and Animation
JavaScript Canvas gives you a way to draw pixels, shapes, text, and images directly in the browser. It is ideal for custom graphics, charts, simple games, visualizations, and effects that are hard to build with normal HTML elements alone.
Quick answer: Canvas is an HTML element that you draw into with JavaScript through a rendering context, usually 2d. You set the canvas size, get the context, and then call drawing methods like fillRect(), arc(), and drawImage().
Difficulty: Beginner
You'll understand this better if you know: basic JavaScript syntax, how the DOM works, and the difference between HTML elements and browser rendering APIs.
1. What Is Canvas?
Canvas is an HTML element used for immediate-mode drawing. Instead of creating separate DOM nodes for each shape, you instruct the browser to paint graphics into a bitmap area. JavaScript then updates that bitmap whenever you redraw.
- It starts as a blank drawing surface.
- JavaScript controls every pixel that appears on it.
- It supports shapes, images, text, gradients, and transformations.
- It is often used with the 2d rendering context, or with WebGL for 3D graphics.
Unlike regular HTML content, canvas does not automatically remember individual objects you drew. If you want to change something, you usually clear and redraw the scene.
2. Why Canvas Matters
Canvas matters because it lets you build graphics that are difficult or inefficient with ordinary HTML and CSS. It is especially useful when the UI needs frequent updates or when the drawing must be treated as one image-like surface.
- Games can redraw moving objects many times per second.
- Charts can render thousands of data points efficiently.
- Image editors can apply effects directly to pixel data.
- Educational tools can draw diagrams and simulations dynamically.
Canvas is not always the right choice. If each graphic element needs its own DOM behavior, accessibility tree, or fine-grained CSS styling, standard HTML elements may be better.
3. Basic Syntax or Core Idea
The core canvas workflow is simple: create a canvas element, get its drawing context, and use that context to draw.
Minimal example
This example shows the smallest useful canvas setup. The important detail is that the drawing happens through the context object, not directly on the canvas element.
<canvas id="myCanvas" width="300" height="150"></canvas>
const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");
ctx.fillStyle = "#4f46e5";
ctx.fillRect(20, 20, 120, 60);The canvas element defines the drawing area. The getContext("2d") call returns the object that provides drawing methods and drawing state such as fill color, stroke color, and line width.
4. Step-by-Step Examples
Example 1: Draw a rectangle
A filled rectangle is the easiest first drawing to make. It uses the current fill style and a position plus size.
const ctx = document.querySelector("canvas").getContext("2d");
ctx.fillStyle = "tomato";
ctx.fillRect(10, 10, 80, 50);This draws a solid tomato-colored rectangle starting at (10, 10). The four numbers are x, y, width, and height.
Example 2: Draw a circle
Canvas does not have a direct circle method. You draw circles by creating an arc path and then filling or stroking it.
const ctx = document.querySelector("canvas").getContext("2d");
ctx.beginPath();
ctx.arc(100, 75, 30, 0, Math.PI * 2);
ctx.fillStyle = "seagreen";
ctx.fill();Here, beginPath() starts a new shape path, and arc() defines a full circle. The final fill() paints it.
Example 3: Draw text
Canvas can render text, but it uses pixel drawing rather than HTML text layout. That means alignment and fonts are controlled through canvas properties.
const ctx = document.querySelector("canvas").getContext("2d");
ctx.font = "20px sans-serif";
ctx.fillStyle = "#111827";
ctx.fillText("Hello Canvas", 20, 40);This writes text at the specified coordinates. The text uses the font and fill style that were active when fillText() was called.
Example 4: Draw an image
One of the most common canvas tasks is displaying an image and then manipulating it or layering drawings on top of it.
const ctx = document.querySelector("canvas").getContext("2d");
const img = new Image();
img.src = "photo.jpg";
img.addEventListener("load", () => {
ctx.drawImage(img, 0, 0, 300, 200);
});The image must load before it can be drawn. The load event ensures the browser has the bitmap ready.
Example 5: Clear and redraw
Canvas often works by redrawing the entire scene on each update. Clearing is a normal part of animation and interaction.
function render(ctx) {
ctx.clearRect(0, 0, 300, 150);
ctx.fillStyle = "#2563eb";
ctx.fillRect(20, 20, 50, 50);
}This pattern is the foundation of animations and moving graphics: erase the old frame, then draw the new one.
5. Practical Use Cases
- Building a game where sprites move every frame.
- Drawing line charts, bar charts, or scatter plots with many points.
- Creating image filters such as grayscale, blur-like effects, or pixel inspection tools.
- Rendering signature pads or drawing apps where the user drags a pointer.
- Showing custom progress indicators and animated loaders.
- Drawing maps, floor plans, or schematic-style visuals.
6. Common Mistakes
Mistake 1: Forgetting to get the rendering context
Canvas drawing methods belong to the context object, not the canvas element itself. Beginners often try to call drawing methods directly on the element.
Problem: This causes errors such as fillRect is not a function because the canvas element does not implement drawing APIs.
const canvas = document.getElementById("game");
canvas.fillRect(10, 10, 50, 50);Fix: Get the 2d context first, then call drawing methods on that object.
const canvas = document.getElementById("game");
const ctx = canvas.getContext("2d");
ctx.fillRect(10, 10, 50, 50);The corrected version works because the context is the object that actually knows how to draw.
Mistake 2: Setting the canvas size with CSS only
Canvas has two sizes: its internal drawing buffer size and its displayed CSS size. If you change only CSS, the image can look blurry or stretched.
Problem: The canvas may appear visually larger, but the drawing still happens in the default internal size, which causes scaling and poor sharpness.
<canvas id="chart" class="wide"></canvas>
/* CSS changes only the display size */
.wide { width: 600px; height: 300px; }Fix: Set the element's width and height attributes to match the intended drawing buffer, and use CSS only for presentation when needed.
<canvas id="chart" width="600" height="300"></canvas>This works because the bitmap buffer now matches the space you plan to draw into.
Mistake 3: Drawing before an image finishes loading
Canvas can only draw image data after the browser has loaded it. If you try too early, nothing appears.
Problem: Calling drawImage() before the image loads can produce a blank result or an InvalidStateError in some situations.
const ctx = document.querySelector("canvas").getContext("2d");
const img = new Image();
img.src = "photo.jpg";
ctx.drawImage(img, 0, 0);Fix: Wait for the load event before drawing the image.
const ctx = document.querySelector("canvas").getContext("2d");
const img = new Image();
img.addEventListener("load", () => {
ctx.drawImage(img, 0, 0);
});
img.src = "photo.jpg";The corrected version works because the image data exists before the draw call runs.
Mistake 4: Forgetting that canvas does not preserve objects for you
Canvas is not a retained scene graph. If you move something and only draw the new position, the old drawing remains unless you clear and redraw.
Problem: The canvas keeps previously drawn pixels, so old positions can remain visible and create visual trails.
let x = 20;
function drawFrame(ctx) {
ctx.fillRect(x, 20, 20, 20);
x += 5;
}Fix: Clear the drawing area before drawing the updated frame.
let x = 20;
function drawFrame(ctx) {
ctx.clearRect(0, 0, 300, 150);
ctx.fillRect(x, 20, 20, 20);
x += 5;
}This version works because each frame starts from a clean drawing surface.
7. Best Practices
Practice 1: Keep drawing and state separate
It is easier to maintain canvas code when the values that describe your scene are stored separately from the code that renders it.
const state = {
x: 40,
y: 50,
radius: 20
};
function render(ctx, state) {
ctx.clearRect(0, 0, 300, 150);
ctx.beginPath();
ctx.arc(state.x, state.y, state.radius, 0, Math.PI * 2);
ctx.fill();
}This approach makes it much easier to update positions, animate objects, and debug your scene.
Practice 2: Use animation frames instead of timers
For smooth animation, the browser's frame scheduler is usually better than fixed-interval timers.
function animate() {
// update state here
// render the new frame here
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);This helps canvas animation stay in sync with the browser's refresh cycle and avoids unnecessary redraws when the tab is inactive.
Practice 3: Match CSS size and drawing buffer size intentionally
If the display size and internal pixel size are different, your drawing can look blurry. Set both deliberately so the result is crisp.
<canvas id="board" width="400" height="200"></canvas>
/* Keep CSS and attribute sizing aligned when possible */
canvas { width: 400px; height: 200px; }When you want a sharp result, the internal buffer should match the size the user actually sees.
8. Limitations and Edge Cases
- Canvas pixels are not semantic content, so accessibility support is limited unless you provide alternative text or nearby controls.
- Canvas does not automatically reflow like HTML. If the container changes size, you often need to resize and redraw manually.
- Text measurement and font rendering can vary slightly across browsers and platforms.
- High-DPI displays may need special sizing logic if you want crisp results on retina screens.
- Once drawn, pixels stay until overwritten. There is no built-in layout engine that manages elements for you.
- Very large canvases can consume a lot of memory because every pixel must be stored in the bitmap buffer.
9. Practical Mini Project
Let's build a tiny progress indicator that fills a bar and writes the percentage as text. This shows how to combine shapes, text, and redraw logic in one canvas.
<canvas id="progressCanvas" width="320" height="120"></canvas>
const canvas = document.getElementById("progressCanvas");
const ctx = canvas.getContext("2d");
let progress = 0;
function render() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#e5e7eb";
ctx.fillRect(20, 40, 280, 24);
ctx.fillStyle = "#22c55e";
ctx.fillRect(20, 40, 280 * progress / 100, 24);
ctx.fillStyle = "#111827";
ctx.font = "16px sans-serif";
ctx.fillText(`Loading: ${progress}%`, 20, 25);
}
function tick() {
progress += 1;
if (progress > 100) {
progress = 0;
}
render();
requestAnimationFrame(tick);
}
render();
requestAnimationFrame(tick);This mini project shows the typical canvas loop: keep state in JavaScript, clear the surface, redraw the scene, and repeat with requestAnimationFrame().
10. Key Points
- Canvas is a drawing surface controlled by JavaScript.
- You usually work through the 2d rendering context.
- Canvas is ideal for dynamic graphics, animation, and custom visuals.
- It draws pixels directly, so you often need to clear and redraw.
- Canvas size attributes and CSS size are not the same thing.
11. Practice Exercise
Create a canvas that draws three shapes: a blue square, a yellow circle, and a black text label. Make sure the shapes do not overlap in a confusing way.
- Use one canvas element with a width of 300 and height of 150.
- Draw a square at the top left.
- Draw a circle near the center.
- Write a short label under the shapes.
Expected output: A simple drawing with three clearly separated items.
Hint: Use fillRect() for the square, arc() plus fill() for the circle, and fillText() for the label.
Solution:
<canvas id="exerciseCanvas" width="300" height="150"></canvas>
const canvas = document.getElementById("exerciseCanvas");
const ctx = canvas.getContext("2d");
// Blue square
ctx.fillStyle = "royalblue";
ctx.fillRect(20, 20, 40, 40);
// Yellow circle
ctx.beginPath();
ctx.arc(150, 50, 20, 0, Math.PI * 2);
ctx.fillStyle = "gold";
ctx.fill();
// Text label
ctx.fillStyle = "#111827";
ctx.font = "16px sans-serif";
ctx.fillText("Canvas practice", 20, 110);This solution works because each shape is drawn in a controlled position with its own drawing call, and the label uses normal text rendering on the same surface.
12. Final Summary
JavaScript Canvas is a browser API for drawing graphics directly into a bitmap surface. It is one of the best tools for custom visuals that change frequently, such as animations, games, charts, and interactive drawing tools.
To use it well, remember the basic pattern: create a canvas element, get the drawing context, draw with that context, and redraw whenever the scene changes. Most beginner problems come from using the wrong object, sizing the canvas incorrectly, or drawing before assets are loaded.
Once you are comfortable with the 2D context, a natural next step is to learn animation loops, image manipulation, and advanced canvas state such as transforms, clipping, and compositing.