While working on an HTML5 canvas project recently, I wanted to fill a background with an elliptical gradient. That is, a radial gradient but with an elliptical shape instead of a perfect circle. There is no native elliptical gradient method in the HTML5 standard, but we can create one without too much trouble by rescaling a (circular) radial gradient. Click on the two screenshots below to see the results.


EllipticalGradient1 screencap
EllipticalGradient2 screencap

Note that in the second example, the gradient colors range from transparent black in the middle to full black at the outer ellipse. When drawn over other graphics on a black background, this creates a soft masking effect. In the first example, the ellipse dimensions were chosen in such a way so that the outermost ellipse intersects the rectangle corners (the ellipse circumscribes the rectangle). In the second example, the outermost ellipse is fully contained in the bounding rectangle (it is inscribed in the rectangle). In both cases, the ellipse dimensions were chosen in such a way that the width-to-height proportions were the same as the proportions for the rectangle.

Download

Download the full source code for both examples here: EllipticalGradient.zip

About the Code

The idea behind the elliptical gradient is fairly simple: scaling down a circular gradient in either the x or y direction will change it to an ellipse shape.

Radial gradients

Creating a circular radial gradient in the canvas is fairly straightforward. We first use the createRadialGradient() method of the CanvasRenderingContext2D object:

var grad = context.createRadialGradient(x0, y0, r0, x1, y1, r1);

which creates a radial gradient with beginning circle at center (x0,y0) and radius r0, and ending circle with center (x1,y1) and radius r1. For many applications, one may wish the beginning (inner) circle to have radius zero.

We add colors to this gradient with the addColorStop() method of the gradient object:

grad.addColorStop(ratio,color);

Here, ratio should be set to a number between 0 and 1, where 0 represents the beginning of the gradient and 1 represents the end of the gradient. You can add as many colors as you like. For example adding colors in this way:

grad.addColorStop(0,"#FF0000");
grad.addColorStop(0.5,"#FFFF00");
grad.addColorStop(1, "#0000FF");

will create a gradient which transitions from red to yellow to blue.

To use the gradient, we set it as the fill style for the context before drawing any graphics with fills:

context.fillStyle = grad;

For example, we may now fill a rectangle with this gradient with the fillRect() method.

The elliptical gradient

In the elliptical gradient code, the center of the gradient is placed at the position (cx, cy), and the size of the gradient is defined by two radii: rx and ry. These define the dimensions of the outermost ellipse of the gradient as shown below:

ellipse

You can easily modify the code to suit your needs by changing these variables at the top of the code.

You can download the commented code and have a look at how the effect is achieved, but here is a quick summary. First, some scaling factors scaleX and scaleY in the x and y directions are calculated based upon the values set for rx and ry. We will draw a circular gradient whose radius is the larger of these two radii, then scale the gradient down in either the x or y direction. For example if rx is larger than ry, then scaleY will be set to ry/rx and scaleX will be set to 1.

This is best understood by a numerical example. Suppose we want to create an elliptical gradient with rx = 200 and ry = 100. To achieve this, we will draw a circular gradient of radius 200, and then scale it down in the y direction by a factor of 2. So scaleX will be set to 1, while scaleY will be set to 0.5.

We have to be careful to keep track of the desired center of the gradient. In this example, if we want the center to be at the position (100,50), then we must draw the circular gradient with center at (100,100), because the resulting scaling in the y direction will transform this center position to the desired center (100,50).

After calculating the scale factors, we set a canvas transformation before drawing the gradient:

context.setTransform(scaleX,0,0,scaleY,0,0);

When we draw a rectangle which will be filled with the gradient (or any other shape), we have to remember that we are dealing with a transformed canvas, so we have to use coordinates and lengths transformed in the inverse way in order to achieve the shape we intend. Here is how the gradient-filled rectangle is drawn in the demos above:

context.fillRect(0,0,rectW*invScaleX,rectH*invScaleY);

Here invScaleX and invScaleY are the reciprocals of the scaling factors scaleX and scaleY, respectively. Note that the zero coordinates did not need to be multiplied because they will remain at zero, but in general if one wishes to draw a rectangle at corner position (x0,y0), width w, and height h, the code must be

context.fillRect(x0*invScaleX,y0*invScaleY,w*invScaleX,h*invScaleY);

Browser differences

At the time of this writing, the gradients are rendering beautifully on my machines in both Firefox and IE9, but in Chrome the quality is a little lower and in fact the sizing seems to be a little incorrect (but the gradient still looks pretty good). In Chrome, the outermost ellipse in the gradient seems to have a radius that is slightly too large. To make up for this, you may wish to adjust your gradient dimensions. Be sure to test in several browsers to see if the results are acceptable!