Colours & Styles
You can draw rectangles, paths, and circles. Now let's make them look good. This lesson covers colour formats, gradients, transparency, and line styling — the visual polish layer that turns shapes into something worth looking at.
Colour formats you already know
If you know CSS, you already know how to specify colours on canvas. Every place canvas accepts a colour — fillStyle, strokeStyle, shadow colours — takes a regular CSS colour string. Named colours, hex codes, rgb(), rgba(), hsl() — they all work.
Try swapping 'coral' for your favourite CSS colour name. Or convert one of the hex values to hsl() — the result should look the same.
Fill vs stroke
You've met fillStyle and strokeStyle already, but let's make the distinction explicit. fillStyle sets the colour used by fill() and fillRect(). strokeStyle sets the colour used by stroke() and strokeRect(). They're completely independent — you can set them to different values and use both on the same shape.
What happens if you call stroke() before fill()? The stroke gets painted first, then the fill paints over half of it. Order matters.
Transparency
There are two ways to make things see-through. The first you've already seen: use rgba() or hsla() with an alpha channel below 1. The second is globalAlpha — it applies to everything drawn after you set it, regardless of the colour format. Set it back to 1.0 when you're done, or everything stays transparent.
Try changing the 0.5 in the rgba() value and the 0.4 in globalAlpha to see how they differ. Can you make the three circles fully opaque? What about almost invisible?
Linear gradients
Solid colours are nice, but gradients are where canvas starts to feel expressive. Call ctx.createLinearGradient(x0, y0, x1, y1) — those four numbers define a line across which the gradient flows, in canvas pixel coordinates. Then add colour stops with addColorStop(offset, colour), where offset goes from 0 (the start) to 1 (the end). The gradient object can be assigned to fillStyle or strokeStyle just like a colour string.
The gradient direction is controlled entirely by the coordinates. Try making the first gradient diagonal by changing (20, 0, 380, 0) to (20, 20, 380, 90). What happens if you add a fourth colour stop at 0.75?
Radial gradients
Radial gradients work similarly, but instead of a line you define two circles: an inner one and an outer one. The gradient radiates outward from the first circle to the second. The signature is createRadialGradient(x0, y0, r0, x1, y1, r1) — the first three parameters define the start circle (centre x, centre y, radius) and the last three define the end circle. Set the inner radius to 0 for a classic “glowing orb” effect.
Move the inner circle off-centre by changing the first 200, 125 to something like 180, 100 — the glow follows. Try increasing the inner radius from 10 to 50 to see how it changes the falloff. Can you add a third orb?
Line styles
So far we've mostly been filling shapes, but stroked lines have a surprising amount of personality. lineWidth controls thickness. lineCap controls what happens at the ends of open paths — 'butt' (flat, default), 'round', or 'square'. lineJoin controls corners — 'miter' (sharp), 'round', or 'bevel'. And setLineDash() lets you create dashed or dotted lines.
Try cranking lineWidth up to 20 on the lineCap demo. Experiment with dash arrays — [2, 2] gives a fine dotted line, [30, 10] gives long dashes. What does setLineDash([]) do? (It resets back to solid.)
Putting it all together
Time to combine everything. Here's a sunset scene that uses linear gradients for the sky and water, a radial gradient for the sun glow, globalAlpha for reflections, and styled lines for the details. Every technique in it is something you've already seen individually. Take it apart, change the colours, break it, rebuild it.
This is yours to play with. Try changing the sky gradient stops to make a sunrise instead (swap the dark-to-warm order). Move the sun higher. Can you add a silhouetted mountain range along the horizon using a path with lineTo calls and a dark fill? What about a crescent moon using two overlapping arc() calls?