Wednesday, October 10, 2012

Understanding HTML5 Canvas Gradients

If you use a paint program, you can fill an object with a solid color, a pattern, or a gradient between one or more colors.  The features exist when using the HTML Canvas API to draw shapes.  However, there are some differences to keep in mind when trying to use these functions.

The first time I used them, I was not fully aware of the concept so it took me a few tries before getting the results I was expecting.  The example on the right illustrates several shapes drawn with gradient fills.  If I were using a drawing program, I'd draw the shape and fill it with a custom gradient defined for each shape.  However, you'll notice each shape has a different gradient that seems to continue through each shape.  That would be pretty difficult to create by filling each shape individually.  In fact, there are actually only two gradients defined in this example:

  1. A red radial gradient that is assigned to the squares running diagonal from the top/left to the bottom/right.

  2. A linear gradient fading from transparent green to opaque blue that is assigned to the remaining shapes.

The gradients are actually defined in the canvas' coordinate space and then each shape assigned that gradient as a fill style will display the portion of the gradient that would be visible at that shapes coordinates.  Its like creating a layer, flood filling it with a gradient fill, and then cutting holes through an opaque layer above it in the shapes you want.  This is not an exact analogy because if you'll notice, the overlapping shapes interact with the transparency defined in the gradient.  So the gradient seems to be copied into the object as well so it can interact with underlying shapes.

To better understand the concept, I decided to render each gradient I used into the whole canvas so I could examine them individually.  Additionally, I plotted some reference shapes on the images to illustrate the control points used in the function calls to create the gradients.

For a radial gradient, I used the following code to construct it:

rfl = ctx.createRadialGradient(100, 100, 10, 150, 150, 250);

rfl.addColorStop(0, 'rgba(255,0,0,1)');
rfl.addColorStop(0.7, 'rgba(255,0,0,0.5)');
rfl.addColorStop(1, 'rgba(255,0,0,0.2)');

Which looks like this:

The function takes two circles to define the start and end points of the gradient.  I plotted those on the reference image.  The function itself is pretty useless without the addColorStop function to setup the actual gradient colors.  The first argument of the function defines the relative point between the two circles that the color transition will be interpolated while the second argument is the color.  In the most simple case, you need two color stop points - the start and end.  I used three in this example to move the transition of the transparency further to the edge of the gradient.  The important thing to notice is that the gradient just doesn't start and end at the defined points.  It actually fills the whole canvas.  What the gradient defines is the space between the start and end.  Anything before the start (inside the small circle) will be the same color that is defined in the addColorStop(0, ...) call.  Anything after the end (outside the larger circle) will be the same color as defined in the addColorStop(1, ...) call.

The linear gradient allows you to really see this concept.  This code defines the gradient on the left:

grd = ctx.createLinearGradient(100,100,150,300);

grd.addColorStop(0, 'rgba(0,255,0,0)');
grd.addColorStop(1, 'rgba(0,0,255,1)');

Which looks like this:

On the image, I plotted the yellow line to illustrate the points passed to the gradient function.  It starts at the top (100,100) and ends at the bottom (150,300).  The two silver lines are perpendicular to the yellow line and define the actual gradient region.  The line simply defines the transition space and could be arbitrarily placed anywhere along those two silver lines to achieve the exact same effect.  Notice how outside the transition region, the color is the same as that defined by the two color stops.  If you assigned this gradient to a shape's fill style, and positioned the shape in the bottom/right corner, it would just look solid blue.  You'll only get the gradient by placing the shape in that area between the silver lines.

The code and example are available in my sandbox. The gradient functionality is quite powerful. You just have to know how to leverage it to achieve the desired effect.