Gradients & Patterns
Solid colors get you only so far. Real depth in canvas graphics — glossy buttons, glowing spheres, sunset skies, textured fills — comes from gradients and patterns. The 2D context lets you build a CanvasGradient from two or more color stops and a CanvasPattern from a repeating image, then assign either one to fillStyle or strokeStyle exactly as you would a color string. This page walks through linear, radial, and conic gradients plus image patterns, with runnable demos you can open in CodePen.
How gradient objects work
A gradient is not drawn directly. You create a gradient object, add color stops to it, then use it as a fill or stroke style. Every gradient factory lives on the 2D context:
| Method | Shape | Key arguments |
|---|---|---|
createLinearGradient(x0, y0, x1, y1) | Linear | Start point and end point of the gradient line |
createRadialGradient(x0, y0, r0, x1, y1, r1) | Radial | Inner circle (center + radius) and outer circle |
createConicGradient(startAngle, x, y) | Conic | Start angle in radians and the center point |
Once you have the object, call addColorStop(offset, color) where offset is a number from 0 (start) to 1 (end). Colors can be any valid CSS color string, including ones with alpha.
Gradient coordinates are in the canvas coordinate space, not relative to the shape you fill. If you move the shape, the gradient stays put unless you recreate it or apply a transform.
Linear gradients
A linear gradient interpolates colors along the straight line from (x0, y0) to (x1, y1). Color stops before 0 or after 1 are clamped, so the first and last colors extend to fill any remaining area.
const ctx = canvas.getContext("2d");
const sky = ctx.createLinearGradient(0, 0, 0, 300);
sky.addColorStop(0, "#0b1d51"); // top: deep night
sky.addColorStop(0.5, "#7b2ff7"); // middle: violet
sky.addColorStop(1, "#ff8a5c"); // bottom: sunset orange
ctx.fillStyle = sky;
ctx.fillRect(0, 0, 400, 300);
Because the start and end share the same x, this gradient runs purely top-to-bottom — ideal for skies. Swap the coordinates to (0, 0, 400, 0) for a horizontal sweep, or use a diagonal for an angled blend.
Radial gradients
A radial gradient blends between two circles. Colors at offset 0 map to the inner circle and offset 1 to the outer circle, which makes it perfect for spheres, spotlights, and glows. Offsetting the inner circle from the outer one produces an off-center highlight that reads as a 3D surface.
// Inner highlight slightly up-left of the sphere center
const sphere = ctx.createRadialGradient(160, 130, 10, 180, 150, 90);
sphere.addColorStop(0, "#ffffff");
sphere.addColorStop(0.2, "#7cc7ff");
sphere.addColorStop(1, "#0a2540");
ctx.fillStyle = sphere;
ctx.beginPath();
ctx.arc(180, 150, 90, 0, Math.PI * 2);
ctx.fill();
Here is both effects together — a gradient sky behind a shaded sphere:
<canvas id="scene" width="400" height="300" style="border:1px solid #333"></canvas>
<script>
const ctx = document.getElementById("scene").getContext("2d");
// Sky
const sky = ctx.createLinearGradient(0, 0, 0, 300);
sky.addColorStop(0, "#0b1d51");
sky.addColorStop(0.6, "#7b2ff7");
sky.addColorStop(1, "#ff8a5c");
ctx.fillStyle = sky;
ctx.fillRect(0, 0, 400, 300);
// Sun / sphere with an off-center highlight
const sun = ctx.createRadialGradient(180, 120, 8, 200, 150, 80);
sun.addColorStop(0, "#fffbe6");
sun.addColorStop(0.3, "#ffd24d");
sun.addColorStop(1, "#ff5e3a");
ctx.fillStyle = sun;
ctx.beginPath();
ctx.arc(200, 150, 80, 0, Math.PI * 2);
ctx.fill();
</script>
Conic gradients
createConicGradient(startAngle, x, y) sweeps colors around a center point, like the hands of a clock. The startAngle is measured in radians, clockwise from the positive x-axis. Conic gradients are great for pie charts, color wheels, and circular progress indicators.
<canvas id="wheel" width="240" height="240" style="border:1px solid #333"></canvas>
<script>
const ctx = document.getElementById("wheel").getContext("2d");
const wheel = ctx.createConicGradient(0, 120, 120);
wheel.addColorStop(0, "#ff0000");
wheel.addColorStop(0.17, "#ffff00");
wheel.addColorStop(0.33, "#00ff00");
wheel.addColorStop(0.5, "#00ffff");
wheel.addColorStop(0.67, "#0000ff");
wheel.addColorStop(0.83, "#ff00ff");
wheel.addColorStop(1, "#ff0000");
ctx.fillStyle = wheel;
ctx.beginPath();
ctx.arc(120, 120, 110, 0, Math.PI * 2);
ctx.fill();
</script>
createConicGradientis supported in all current evergreen browsers but is newer than the other two. For wide legacy support, fall back to drawing colored wedges witharc.
Patterns from images
createPattern(image, repetition) tiles an image (or another canvas or video frame) across whatever you fill. The source can be an HTMLImageElement, HTMLCanvasElement, ImageBitmap, or HTMLVideoElement. The repetition argument controls tiling:
| Value | Behavior |
|---|---|
"repeat" | Tile in both directions (default) |
"repeat-x" | Tile horizontally only |
"repeat-y" | Tile vertically only |
"no-repeat" | Draw once |
Because the image must be fully loaded before it can be tiled, create the pattern inside the image’s load handler:
const ctx = canvas.getContext("2d");
const tile = new Image();
tile.src = "brick.png";
tile.addEventListener("load", () => {
const pattern = ctx.createPattern(tile, "repeat");
ctx.fillStyle = pattern;
ctx.fillRect(0, 0, canvas.width, canvas.height);
});
You can also generate a pattern from an off-screen canvas, which avoids loading external assets entirely:
// Build a 20x20 checkerboard tile in memory
const tile = document.createElement("canvas");
tile.width = tile.height = 20;
const tctx = tile.getContext("2d");
tctx.fillStyle = "#222";
tctx.fillRect(0, 0, 10, 10);
tctx.fillRect(10, 10, 10, 10);
const pattern = ctx.createPattern(tile, "repeat");
ctx.fillStyle = pattern;
ctx.fillRect(0, 0, 300, 200);
console.log(pattern instanceof CanvasPattern);
Output:
true
A pattern’s tiling is anchored to the canvas origin, not the filled shape. Use pattern.setTransform(matrix) (passing a DOMMatrix) to offset or scale the tiling independently of the rest of your drawing.
Best Practices
- Create gradient and pattern objects once and reuse them across frames; rebuilding them every frame is wasteful in animation loops.
- Remember that gradient and pattern coordinates are in canvas space — recreate the gradient or apply a transform when the target shape moves.
- Keep color stops between
0and1; out-of-range stops are clamped and rarely do what you expect. - Use alpha-enabled color stops (for example
rgba(0,0,0,0)) for soft fades, vignettes, and glows. - Always wait for
image.onload(orawait image.decode()) before callingcreatePattern, or you may get a blank fill. - Prefer an in-memory canvas tile for simple repeating textures to avoid an extra network request.