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