Drawing Rectangles
Rectangles are the only shape the Canvas 2D API can draw with a single dedicated method call — no paths required. That makes them the perfect starting point for learning how the context renders, styles, and erases pixels. Once you understand fillRect, strokeRect, and clearRect, the same fillStyle, strokeStyle, and lineWidth properties carry over to every other drawing operation you’ll ever do on a canvas.
The three rectangle methods
The rendering context exposes exactly three rectangle methods, each taking the same four arguments: an x and y for the top-left corner, plus a width and height. Coordinates are measured in pixels from the top-left of the canvas, with the y-axis pointing down.
| Method | What it does |
|---|---|
fillRect(x, y, w, h) | Paints a solid rectangle using the current fillStyle |
strokeRect(x, y, w, h) | Outlines a rectangle using the current strokeStyle and lineWidth |
clearRect(x, y, w, h) | Erases the pixels in the region, making them transparent |
Unlike path-based drawing, these methods draw immediately — there is no beginPath() or fill() step to call afterward.
const canvas = document.querySelector("#scene");
const ctx = canvas.getContext("2d");
ctx.fillStyle = "#2563eb";
ctx.fillRect(20, 20, 120, 80); // solid blue box
ctx.strokeStyle = "#dc2626";
ctx.lineWidth = 4;
ctx.strokeRect(170, 20, 120, 80); // red outlined box
Setting fill and stroke styles
fillStyle controls the color used by fillRect, while strokeStyle controls the outline color used by strokeRect. Both accept any valid CSS color string — named colors, hex, rgb(), rgba(), or hsl() — and both default to opaque black (#000000).
These properties are stateful: once you set them, every subsequent draw call uses that value until you change it again. Set the style first, then draw.
ctx.fillStyle = "rgba(34, 197, 94, 0.5)"; // semi-transparent green
ctx.fillRect(0, 0, 100, 100);
ctx.fillStyle = "hsl(280 80% 60%)"; // purple
ctx.fillRect(120, 0, 100, 100);
Tip: A common beginner bug is calling
fillRectbefore settingfillStyle, then wondering why everything is black. Because state persists, always set the style on the line directly above the draw call until the order becomes second nature.
Controlling outline thickness
lineWidth sets the stroke thickness in pixels for strokeRect (and all path strokes). The line is centered on the rectangle’s edge, so half of the width sits inside the bounds and half sits outside. A lineWidth of 10 on a 100×100 rectangle therefore covers from -5 to 105 along each edge.
ctx.strokeStyle = "#111827";
ctx.lineWidth = 1;
ctx.strokeRect(20, 20, 80, 80);
ctx.lineWidth = 8;
ctx.strokeRect(140, 20, 80, 80);
Warning: Thin strokes can look blurry because the centered line straddles two pixel rows. For a crisp 1px border, offset the coordinates by half a pixel — e.g.
strokeRect(20.5, 20.5, 80, 80).
Erasing with clearRect
clearRect does the opposite of filling: it resets the pixels in a region to fully transparent, revealing whatever is behind the canvas (the page background, by default). This is the standard way to wipe the canvas before each frame of an animation.
// Clear the entire canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
You can also clear a smaller region to “punch a hole” in an existing fill, which is handy for masking or simple sprite erasing.
A complete interactive demo
The pen below combines all three methods plus styling. It draws a backdrop, an outlined frame, and then clears a window out of the fill.
<canvas id="demo" width="320" height="200" style="background:#0f172a"></canvas>
<script>
const ctx = document.getElementById("demo").getContext("2d");
// Solid fill
ctx.fillStyle = "#38bdf8";
ctx.fillRect(40, 30, 240, 140);
// Outline on top
ctx.strokeStyle = "#f97316";
ctx.lineWidth = 6;
ctx.strokeRect(40, 30, 240, 140);
// Erase a window in the middle
ctx.clearRect(120, 70, 80, 60);
</script>
Building a grid
Because the rectangle methods are so cheap, they’re ideal for generating repeating layouts like grids, checkerboards, or bar charts. Loop over rows and columns and compute each cell’s position.
<canvas id="grid" width="320" height="320"></canvas>
<script>
const ctx = document.getElementById("grid").getContext("2d");
const cols = 8;
const cell = 320 / cols;
for (let row = 0; row < cols; row++) {
for (let col = 0; col < cols; col++) {
const isDark = (row + col) % 2 === 0;
ctx.fillStyle = isDark ? "#1e293b" : "#e2e8f0";
ctx.fillRect(col * cell, row * cell, cell, cell);
}
}
</script>
When you run a grid like this, no console output is produced — the result is purely visual. If you log the cell size for debugging:
console.log(`Each cell is ${320 / 8}px square`);
Output:
Each cell is 40px square
Best Practices
- Set
fillStyle,strokeStyle, andlineWidthbefore the draw call, and remember they persist across calls until reassigned. - Use
clearRect(0, 0, canvas.width, canvas.height)to wipe the whole canvas at the start of each animation frame. - Offset coordinates by
0.5pixels when you need a sharp 1px border to avoid anti-aliasing blur. - Draw fills first and strokes second so the outline sits crisply on top of the solid color.
- Avoid resetting styles you haven’t changed — minimizing state changes keeps rendering fast in tight loops.
- Cache
canvas.getContext("2d")in a variable rather than calling it repeatedly.