Gradients

Fill Style with Linear and Radial Gradients

createLinearGradient(x0, y0, x1, y1) and createRadialGradient(x0, y0, r0, x1, y1, r1) return gradients; addColorStop(offset, color) adds stops from 0 to 1. Unlike SVG's bounding-box default (Linear and Radial Gradients), canvas gradients live in canvas coordinates, so shapes sharing one gradient show slices of one ramp.

A dusk cover, one gradient across two shapes, and a lit sphereHTML
<canvas id="c" style="width:600px; height:104px"></canvas><script src="hidpi.js"></script>
<script>
  const ctx = hidpi(c);
  const dusk = ctx.createLinearGradient(0, 4, 0, 100);          // top to bottom
  [[0, '#1f5f8b'], [0.6, '#5b3f99'], [1, '#e09a10']].forEach(s => dusk.addColorStop(...s));
  ctx.fillStyle = dusk;  ctx.fillRect(4, 4, 72, 96);
  const band = ctx.createLinearGradient(120, 0, 420, 0);         // one ramp, two bars
  band.addColorStop(0, '#3f7d3a');  band.addColorStop(1, '#fff3e0');
  ctx.fillStyle = band;  ctx.fillRect(120, 10, 120, 36);  ctx.fillRect(300, 58, 120, 36);
  const ball = ctx.createRadialGradient(505, 38, 5, 520, 52, 48);   // off-center inner circle
  ball.addColorStop(0, '#fff');  ball.addColorStop(1, '#b5452f');
  ctx.fillStyle = ball;  ctx.beginPath();  ctx.arc(520, 52, 48, 0, 2 * Math.PI);  ctx.fill();
</script>
Browser output of Listing 2.8
Browser output of 8

The second bar starts where the first one's ramp left off. To give each shape its own ramp, build the gradient from the shape's position or translate the context first (Translate, Rotate, Scale). An off-center inner circle lights the ball.

Linear and radial gradients live in canvas coordinates, so two bars share one rampHTMLLive
<!doctype html>
<style>
  body { margin: 0; padding: 8px; font: 13px system-ui, sans-serif; background: #fafaf7; }
  canvas { width: 100%; max-width: 600px; height: 150px; display: block; background: #fff; border: 1px solid #ddd; }
</style>
<canvas id="c"></canvas>
<p>Move the pointer over the ball to move its highlight.</p>
<script>
  function hidpi(c, r = devicePixelRatio) {
    c.width = c.clientWidth * r;  c.height = c.clientHeight * r;
    const ctx = c.getContext('2d');  ctx.scale(r, r);  return ctx;
  }
  const c = document.getElementById('c');
  const ctx = hidpi(c);
  let light = [505, 55];

  function draw() {
    ctx.clearRect(0, 0, 600, 150);
    // A dusk sky: vertical linear gradient with three stops
    const dusk = ctx.createLinearGradient(0, 10, 0, 130);
    [[0, '#1f5f8b'], [0.6, '#5b3f99'], [1, '#e09a10']].forEach(s => dusk.addColorStop(...s));
    ctx.fillStyle = dusk;  ctx.fillRect(10, 10, 80, 120);

    // One horizontal ramp from x=120 to x=420, used by two bars:
    // the second bar continues where the first left off
    const band = ctx.createLinearGradient(120, 0, 420, 0);
    band.addColorStop(0, '#3f7d3a');  band.addColorStop(1, '#fff3e0');
    ctx.fillStyle = band;
    ctx.fillRect(120, 20, 120, 45);  ctx.fillRect(300, 80, 120, 45);
    ctx.strokeStyle = '#ccc';  ctx.strokeRect(120, 20, 300, 105);

    // A radial gradient with an off-center inner circle lights the ball
    const ball = ctx.createRadialGradient(light[0], light[1], 5, 520, 75, 55);
    ball.addColorStop(0, '#fff');  ball.addColorStop(1, '#b5452f');
    ctx.fillStyle = ball;
    ctx.beginPath();  ctx.arc(520, 75, 55, 0, 2 * Math.PI);  ctx.fill();

    ctx.fillStyle = '#263238';  ctx.font = '11px sans-serif';
    ctx.fillText('one gradient, two bars', 250, 142);
  }
  c.addEventListener('pointermove', e => {
    const b = c.getBoundingClientRect(), x = e.clientX - b.left, y = e.clientY - b.top;
    if (Math.hypot(x - 520, y - 75) < 50) { light = [x, y];  draw(); }
  });
  draw();
</script>