# HTML canvas gradient: createLinearGradient, radial and conic

[Canonical HTML page](https://gradiently.design/guide/canvas-gradient)

Canvas gradients take coordinates where CSS takes angles, and that one difference causes most of the confusion. Here is every gradient method with code that runs, and a helper that turns a CSS angle into canvas points.

## 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.

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.

```js
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 smallest working canvas gradient: a diagonal from the top left corner to the bottom right.

## The three gradient methods

| Method | Arguments | CSS equivalent |
| --- | --- | --- |
| `createLinearGradient` | `x0, y0, x1, y1` | `linear-gradient()` |
| `createRadialGradient` | `x0, y0, r0, x1, y1, r1` | `radial-gradient()` |
| `createConicGradient` | `startAngle, x, y` | `conic-gradient()` |
| `addColorStop` | `offset` 0 to 1, `colour` | A colour stop with a percentage |

Every method returns a CanvasGradient. You can keep it in a variable and reuse it for as many shapes as you like.

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](https://gradiently.design/guide/css-linear-gradient) renders identically on canvas.

```js
// 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)
```

In CSS, 0deg points up and angles turn clockwise. The length formula makes the corners land exactly on the first and last colours, as CSS does.

The same gradient in CSS: `linear-gradient(135deg, #0f172a 0%, #0d9488 100%)`

What the helper above draws on a 1200×630 canvas, the size of an [Open Graph image](https://gradiently.design/guide/open-graph-tags). If your angles look off, [gradient angle](https://gradiently.design/guide/gradient-angle) explains how CSS measures them.

## 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.

```js
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)
```

Fading the last stop to transparent lets you stack several lights on one ground, which is how layered backgrounds are built.

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](https://gradiently.design/guide/css-radial-gradient).

## 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`.

```js
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()
```

Repeating the first colour at offset 1 hides the seam. createConicGradient works in current Chrome, Edge, Safari and Firefox.

- createLinearGradient: `linear-gradient(135deg, #1e1b4b 0%, #7c3aed 55%, #f472b6 100%)`
- createRadialGradient: `radial-gradient(circle at 75% 20%, #7c3aed 0%, #2e1065 30%, #0c0a1d 60%)`
- createConicGradient: `conic-gradient(from 0deg, #22d3ee, #7c3aed, #f472b6, #22d3ee)`

The three examples from this article, shown as their CSS twins. [CSS conic gradients](https://gradiently.design/guide/css-conic-gradient) covers the conic version in more depth.

## 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.

```js
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)
```

You cannot move or remove a stop once it is added, so animate by creating a new gradient each frame. Pause the loop for users who prefer reduced motion.

## 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](https://gradiently.design/guide/png-vs-jpeg) explains the trade. Draw at the real output size, such as 1200×630 for a link preview, rather than scaling a small canvas up.

```js
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')
```

Download the finished canvas as a PNG. On a server, the same drawing code runs with a canvas library for Node.

## Common canvas gradient problems

### Symptom

- Gradient looks blurry on a phone
- Only one colour shows in a shape
- Visible stripes in a dark gradient
- `IndexSizeError` from addColorStop
- Conic gradient starts in the wrong place

### Fix

- Multiply canvas width and height by `devicePixelRatio`, then `ctx.scale()` back
- Create the gradient with the shape's coordinates, not the canvas's
- Add light noise over the top, see [gradient banding](https://gradiently.design/guide/gradient-banding)
- Keep offsets between 0 and 1
- Start at `-Math.PI / 2` to match CSS

> **Wider colour on capable screens** Chrome and Safari accept `canvas.getContext('2d', { colorSpace: 'display-p3' })`, which lets vivid gradients use the wider P3 range. Browsers that do not support it fall back to sRGB.

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](https://gradiently.design/guide/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.

## FAQ

### 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.
