The short version
- An HTML canvas gradient is a CanvasGradient object made with createLinearGradient, createRadialGradient or createConicGradient, given colour stops with addColorStop, and assigned to fillStyle or strokeStyle.
- Canvas gradients use coordinates in canvas space, not angles or percentages, so a linear gradient needs a start point and an end point.
- Colour stop offsets run from 0 to 1, and any CSS colour string is accepted, including hex, rgb(), hsl() and oklch() in current browsers.
- createConicGradient starts its sweep from a line pointing right and measures the angle in radians, unlike CSS conic-gradient, which starts at the top in degrees.
- On high density screens, size the canvas buffer by devicePixelRatio, or gradients and text look soft.
On this page
An HTML canvas gradient takes three steps: create a gradient object with ctx.createLinearGradient(), createRadialGradient() or createConicGradient(), add colours with addColorStop(offset, colour), then assign it to ctx.fillStyle and draw. Unlike CSS, a canvas gradient is defined by points in pixels, so you tell it exactly where the colour starts and ends. Everything you paint with that fill picks up the part of the gradient underneath it.
const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
const g = ctx.createLinearGradient(0, 0, canvas.width, canvas.height)
g.addColorStop(0, '#1e1b4b')
g.addColorStop(0.55, '#7c3aed')
g.addColorStop(1, '#f472b6')
ctx.fillStyle = g
ctx.fillRect(0, 0, canvas.width, canvas.height)The three gradient methods
createLinearGradientArguments
x0, y0, x1, y1CSS equivalent
linear-gradient()createRadialGradientArguments
x0, y0, r0, x1, y1, r1CSS equivalent
radial-gradient()createConicGradientArguments
startAngle, x, yCSS equivalent
conic-gradient()addColorStopArguments
offset 0 to 1, colourCSS equivalent
The gradient is fixed to the canvas, not to the shape. If you create a gradient from 0 to 400 and then fill a small square at x 300, the square shows only the pink end. That surprises people coming from CSS, where a gradient always stretches to the element. Create the gradient with the shape's own coordinates when you want it to fit.
createLinearGradient and CSS angles
CSS lets you write 135deg. Canvas wants two points. This helper converts a CSS angle into the same start and end points CSS uses, so a design written as a CSS linear gradient renders identically on canvas.
// Match CSS linear-gradient(angle) on a w by h rectangle
function cssAngleGradient(ctx, angleDeg, x, y, w, h) {
const a = (angleDeg * Math.PI) / 180
const len = Math.abs(w * Math.sin(a)) + Math.abs(h * Math.cos(a))
const cx = x + w / 2
const cy = y + h / 2
const dx = (Math.sin(a) * len) / 2
const dy = (-Math.cos(a) * len) / 2
return ctx.createLinearGradient(cx - dx, cy - dy, cx + dx, cy + dy)
}
const g = cssAngleGradient(ctx, 135, 0, 0, 1200, 630)
g.addColorStop(0, '#0f172a')
g.addColorStop(1, '#0d9488')
ctx.fillStyle = g
ctx.fillRect(0, 0, 1200, 630)linear-gradient(135deg, #0f172a 0%, #0d9488 100%)createRadialGradient
A radial gradient on canvas is defined by two circles: a start circle and an end circle. Colour stop 0 sits on the first circle and stop 1 on the second. Most of the time you want the first circle to be a point, so its radius is 0.
const w = canvas.width
const h = canvas.height
// A soft light in the top right corner of a dark ground
ctx.fillStyle = '#0c0a1d'
ctx.fillRect(0, 0, w, h)
const glow = ctx.createRadialGradient(w * 0.75, h * 0.2, 0, w * 0.75, h * 0.2, w * 0.6)
glow.addColorStop(0, 'rgb(124 58 237 / 0.9)')
glow.addColorStop(0.5, 'rgb(46 16 101 / 0.5)')
glow.addColorStop(1, 'rgb(12 10 29 / 0)')
ctx.fillStyle = glow
ctx.fillRect(0, 0, w, h)Move the centre of the first circle away from the second and the light tilts, like a spotlight hitting a wall at an angle. CSS has no direct equivalent of that offset. For the CSS side, see CSS radial gradients.
createConicGradient
createConicGradient(startAngle, x, y) sweeps colour around a centre point. Two details differ from CSS: the angle is in radians, and 0 points to the right (three o'clock), not to the top. To match a CSS conic-gradient(from 0deg, ...), start at -Math.PI / 2.
const cx = canvas.width / 2
const cy = canvas.height / 2
const ring = ctx.createConicGradient(-Math.PI / 2, cx, cy)
ring.addColorStop(0, '#22d3ee')
ring.addColorStop(0.33, '#7c3aed')
ring.addColorStop(0.66, '#f472b6')
ring.addColorStop(1, '#22d3ee')
ctx.fillStyle = ring
ctx.beginPath()
ctx.arc(cx, cy, 160, 0, Math.PI * 2)
ctx.fill()Gradient text, strokes and animation
fillStyle and strokeStyle both accept a gradient, so text and outlines work the same way. Set the gradient across the text's own box, measured with measureText(), so the full range of colours appears inside the letters.
ctx.font = '700 96px Inter, system-ui, sans-serif'
const text = 'Open late'
const width = ctx.measureText(text).width
const tg = ctx.createLinearGradient(80, 0, 80 + width, 0)
tg.addColorStop(0, '#fde68a')
tg.addColorStop(1, '#fb7185')
ctx.fillStyle = tg
ctx.fillText(text, 80, 200)
// Animation: rebuild the gradient each frame, it is cheap
function frame(t) {
const shift = (Math.sin(t / 2000) + 1) / 2
const g = ctx.createLinearGradient(0, 0, canvas.width, 0)
g.addColorStop(0, '#1e1b4b')
g.addColorStop(0.2 + shift * 0.6, '#7c3aed')
g.addColorStop(1, '#0f172a')
ctx.fillStyle = g
ctx.fillRect(0, 0, canvas.width, canvas.height)
requestAnimationFrame(frame)
}
requestAnimationFrame(frame)Saving the canvas as an image
A common reason to draw gradients on canvas is to generate an image: a share card, a certificate, a cover. Call canvas.toBlob() with image/png for gradients and text, because PNG keeps smooth blends intact, and use JPEG only when the image is mostly photograph. PNG vs JPG explains the trade. Draw at the real output size, such as 1200×630 for a link preview, rather than scaling a small canvas up.
canvas.toBlob((blob) => {
const a = document.createElement('a')
a.href = URL.createObjectURL(blob)
a.download = 'card.png'
a.click()
setTimeout(() => URL.revokeObjectURL(a.href), 1000)
}, 'image/png')Common canvas gradient problems
Symptom
- Gradient looks blurry on a phone
- Only one colour shows in a shape
- Visible stripes in a dark gradient
IndexSizeErrorfrom addColorStop- Conic gradient starts in the wrong place
Fix
- Multiply canvas width and height by
devicePixelRatio, thenctx.scale()back - Create the gradient with the shape's coordinates, not the canvas's
- Add light noise over the top, see gradient banding
- Keep offsets between 0 and 1
- Start at
-Math.PI / 2to match CSS
When you need gradients that move with real lighting rather than flat blends, a canvas reaches its limits and WebGL takes over, covered in shader gradients. And when the canvas is only a means to an image, such as a social card or poster, Gradiently renders a Mark at any size from 200 to 8,000 px with the words laid out for you, so there is no drawing code to maintain.
Questions people ask
How do I make a gradient on an HTML canvas?
Call ctx.createLinearGradient(x0, y0, x1, y1), add colours with addColorStop(offset, colour), set ctx.fillStyle to the gradient and draw a shape such as fillRect.
What is the difference between createLinearGradient and CSS linear-gradient?
Canvas uses a start point and an end point in pixels, while CSS uses an angle and stretches the gradient to the element. A small helper can convert a CSS angle into canvas points.
Can I use oklch or hsl colours in addColorStop?
Yes. addColorStop accepts any CSS colour string the browser understands, including hex, rgb(), hsl() and, in current browsers, oklch().
Why does my canvas gradient only show one colour?
The gradient is positioned in canvas coordinates, so a shape drawn outside its start and end points only picks up the nearest end colour. Create the gradient using the shape's position and size.
How do I animate a canvas gradient?
Create a new gradient on each animation frame with updated stops or coordinates, and redraw. A gradient's stops cannot be moved or removed once added.
Written by Gradiently
The team behind Gradiently, a design tool built around Marks: living gradients that make everything you design look like yours.
See our profile