# HTML-canvasin liukuväri: lineaarinen, säteittäinen ja kartiomainen

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

Canvas-liukuväri käyttää koordinaatteja CSS:n kulmien sijaan. Tämä ero aiheuttaa eniten hämmennystä. Tässä kaikki menetelmät toimivine koodeineen ja apufunktio CSS-kulman muuttamiseen canvas-pisteiksi.

## The short version

- HTML-canvasin liukuväri on CanvasGradient-olio, joka tehdään createLinearGradient-, createRadialGradient- tai createConicGradient-metodilla, väritetään addColorStop-metodilla ja asetetaan fillStyle- tai strokeStyle-arvoksi.
- Canvas-liukuvärit käyttävät canvasin koordinaatteja kulmien ja prosenttien sijaan, joten lineaarinen liukuväri tarvitsee alku- ja loppupisteen.
- Väripisteiden sijainnit ovat välillä 0 ja 1. Kaikki CSS-värimerkkijonot kelpaavat, mukaan lukien hex, rgb(), hsl() ja nykyisissä selaimissa oklch().
- createConicGradient aloittaa oikealle osoittavalta viivalta ja mittaa kulman radiaaneina. CSS:n conic-gradient aloittaa ylhäältä ja käyttää asteita.
- Tiheillä näytöillä canvasin puskurikoko pitää määrittää devicePixelRatio-arvon mukaan, muuten liukuvärit ja teksti näyttävät pehmeiltä.

**HTML-canvasin liukuväri** syntyy kolmessa vaiheessa: luo liukuväriolio metodilla `ctx.createLinearGradient()`, `createRadialGradient()` tai `createConicGradient()`, lisää värit metodilla `addColorStop(offset, colour)`, aseta olio `ctx.fillStyle`-arvoksi ja piirrä. CSS:stä poiketen canvas-liukuväri määritetään pikselipisteillä, joten kerrot tarkasti, mistä väri alkaa ja mihin se päättyy. Kaikki tällä täytöllä maalaamasi saa alleen osuvan liukuvärin osan.

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

Pienin toimiva canvas-liukuväri: viistosti vasemmasta yläkulmasta oikeaan alakulmaan.

## Kolme liukuvärimenetelmää

| Metodi | Argumentit | CSS-vastine |
| --- | --- | --- |
| `createLinearGradient` | `x0, y0, x1, y1` | `linear-gradient()` |
| `createRadialGradient` | `x0, y0, r0, x1, y1, r1` | `radial-gradient()` |
| `createConicGradient` | `startAngle, x, y` | `conic-gradient()` |
| `addColorStop` | `offset` välillä 0 ja 1, `colour` | Väripiste prosenttiluvulla |

Jokainen metodi palauttaa CanvasGradient-olion. Voit tallentaa sen muuttujaan ja käyttää haluamaasi määrään muotoja.

Liukuväri on sidottu canvasiin, ei muotoon. Jos luot liukuvärin välille 0 ja 400 ja täytät pienen neliön kohdassa x 300, neliö näyttää vain pinkin lopun. Tämä yllättää CSS:stä tulevan, sillä CSS-liukuväri venyy aina elementtiin. Luo liukuväri muodon omilla koordinaateilla, kun haluat sen sopivan muotoon.

## createLinearGradient ja CSS-kulmat

CSS:ssä voit kirjoittaa `135deg`. Canvas haluaa kaksi pistettä. Tämä apufunktio muuttaa CSS-kulman samoiksi alku- ja loppupisteiksi kuin CSS käyttää. Näin [CSS:n lineaarisena liukuvärinä](https://gradiently.design/fi/guide/css-linear-gradient) kirjoitettu design piirtyy canvasille samanlaisena.

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

CSS:ssä 0deg osoittaa ylöspäin ja kulmat kiertyvät myötäpäivään. Pituuskaava asettaa kulmiin täsmälleen ensimmäisen ja viimeisen värin kuten CSS.

Sama liukuväri CSS:ssä: `linear-gradient(135deg, #0f172a 0%, #0d9488 100%)`

Yllä oleva apufunktio piirtää tämän 1200×630-canvasiin, joka on [Open Graph -kuvan](https://gradiently.design/fi/guide/open-graph-tags) koko. Jos kulmat näyttävät vääriltä, [liukuvärin kulma](https://gradiently.design/fi/guide/gradient-angle) selittää CSS:n mittaustavan.

## createRadialGradient

Canvasin säteittäinen liukuväri määritetään kahdella ympyrällä: alku- ja loppuympyrällä. Väripiste 0 on ensimmäisellä ja väripiste 1 toisella. Yleensä ensimmäinen ympyrä on piste, joten sen säde on 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)
```

Viimeisen väripisteen häivyttäminen läpinäkyväksi sallii useiden valojen kerrostamisen yhdelle pohjalle. Näin kerrokselliset taustat rakennetaan.

Siirrä ensimmäisen ympyrän keskusta pois toisen keskuksesta, niin valo kallistuu kuin seinään vinosti osuva kohdevalo. CSS:ssä ei ole suoraa vastinetta tälle siirtymälle. Katso CSS-puolelta [säteittäiset CSS-liukuvärit](https://gradiently.design/fi/guide/css-radial-gradient).

## createConicGradient

`createConicGradient(startAngle, x, y)` kiertää väriä keskipisteen ympäri. Kaksi asiaa eroaa CSS:stä: kulma on radiaaneina ja 0 osoittaa oikealle (kello kolmeen), ei ylös. Vastaa CSS:n arvoa `conic-gradient(from 0deg, ...)` aloittamalla kohdasta `-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()
```

Ensimmäisen värin toistaminen kohdassa 1 kätkee sauman. createConicGradient toimii nykyisissä Chromessa, Edgessä, Safarissa ja Firefoxissa.

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

Artikkelin kolme esimerkkiä CSS-vastineinaan. [Kartiomaiset CSS-liukuvärit](https://gradiently.design/fi/guide/css-conic-gradient) käsittelee kartioversiota tarkemmin.

## Liukuväriteksti, ääriviivat ja animaatio

Sekä `fillStyle` että `strokeStyle` hyväksyvät liukuvärin, joten teksti ja ääriviivat toimivat samoin. Aseta liukuväri tekstin omalle alueelle, joka mitataan `measureText()`-metodilla, jotta koko värikirjo näkyy kirjaimissa.

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

Lisättyä väripistettä ei voi siirtää tai poistaa, joten animoi luomalla uusi liukuväri joka ruudussa. Keskeytä silmukka käyttäjille, jotka suosivat vähennettyä liikettä.

## Canvasin tallennus kuvana

Canvasille piirretään liukuvärejä usein kuvan luomiseksi: jakokortiksi, todistukseksi tai kanneksi. Kutsu `canvas.toBlob()` tyypillä `image/png` liukuväreille ja tekstille, sillä PNG säilyttää sileät siirtymät. Käytä JPEG:iä vain, kun kuva on pääosin valokuva. [PNG ja JPG](https://gradiently.design/fi/guide/png-vs-jpeg) selittää kompromissin. Piirrä todellisessa vientikoossa, kuten 1200×630 linkkiesikatselulle, älä suurenna pientä canvasia.

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

Lataa valmis canvas PNG:nä. Palvelimella sama piirtokoodi toimii Noden canvas-kirjastolla.

## Tavalliset canvas-liukuvärin ongelmat

### Oire

- Liukuväri näyttää sumealta puhelimessa
- Muodossa näkyy vain yksi väri
- Tummassa liukuvärissä näkyy raitoja
- addColorStop antaa `IndexSizeError`-virheen
- Kartioliukuväri alkaa väärästä kohdasta

### Korjaus

- Kerro canvasin leveys ja korkeus `devicePixelRatio`-arvolla ja skaalaa takaisin `ctx.scale()`-metodilla
- Luo liukuväri muodon, ei canvasin, koordinaateilla
- Lisää päälle kevyttä kohinaa, katso [liukuvärin raidoittuminen](https://gradiently.design/fi/guide/gradient-banding)
- Pidä sijainnit välillä 0 ja 1
- Aloita kohdasta `-Math.PI / 2`, jotta tulos vastaa CSS:ää

> **Laajemmat värit niitä tukevilla näytöillä** Chrome ja Safari hyväksyvät kutsun `canvas.getContext('2d', { colorSpace: 'display-p3' })`, jolloin kirkkaat liukuvärit voivat käyttää laajempaa P3-värialuetta. Muut selaimet palaavat sRGB:hen.

Kun tarvitset aidon valaistuksen mukana liikkuvia liukuvärejä tasaisten siirtymien sijaan, canvasin rajat tulevat vastaan ja WebGL jatkaa. [Shader-liukuvärit](https://gradiently.design/fi/guide/shader-gradients) käsittelee tätä. Jos canvas on vain väline somekortin tai julisteen kuvaan, Gradiently piirtää Markin missä tahansa koossa 200 ja 8 000 px:n välillä ja sommittelee sanat puolestasi. Ylläpidettävää piirtokoodia ei tarvita.

## FAQ

### Miten teen liukuvärin HTML-canvasiin?

Kutsu `ctx.createLinearGradient(x0, y0, x1, y1)`, lisää värit metodilla `addColorStop(offset, colour)`, aseta liukuväri `ctx.fillStyle`-arvoksi ja piirrä muoto esimerkiksi `fillRect`-metodilla.

### Mitä eroa on createLinearGradientilla ja CSS:n linear-gradientilla?

Canvas käyttää alku- ja loppupistettä pikseleinä, CSS kulmaa ja venyttää liukuvärin elementtiin. Pieni apufunktio voi muuntaa CSS-kulman canvas-pisteiksi.

### Voinko käyttää oklch- tai hsl-värejä addColorStopissa?

Kyllä. addColorStop hyväksyy kaikki selaimen ymmärtämät CSS-värimerkkijonot, kuten hex, rgb(), hsl() ja nykyisissä selaimissa oklch().

### Miksi canvas-liukuvärissä näkyy vain yksi väri?

Liukuväri sijoittuu canvasin koordinaatteihin, joten alku- ja loppupisteiden ulkopuolelle piirretty muoto saa vain lähimmän päätyvärin. Luo liukuväri muodon sijainnin ja koon mukaan.

### Miten animoin canvas-liukuvärin?

Luo joka animaatioruudussa uusi liukuväri päivitetyillä väripisteillä tai koordinaateilla ja piirrä uudelleen. Lisättyjä väripisteitä ei voi siirtää tai poistaa.
