It is possible to define the stroke or the fill style as a "gradient", a set of interpolated colors, like in this example below:
A linear gradient is seen as an "invisible" rectangle in which a set of colors are interpolated along a line.
The gradient only becomes visible when we draw shapes within it, where it has been specified in the fillStyle or strokeStyle property.
There are 3 steps:
Syntax:
ctx.createLinearGradient(x0,y0,x1,y1);
... where the (x0, y0) and (x1, y1) parameters define "the direction of the gradient" (as a vector with a starting and an ending point). This direction is an invisible line along which the colors that compose the gradient will be interpolated.
Let's see an example:
grdFrenchFlag = ctx.createLinearGradient(0, 0, 300, 0);
This line defines the direction of the gradient: a virtual, invisible line that goes from the top left corner of the canvas (0, 0) to the top right corner of the canvas (300, 0). The interpolated colors will propagate along this line.
If this gradient is going to be reused by different functions, it is good practice to create/initialize it in a function called when the page is loaded and to store it in a global variable.
We will add a set of "colors" and "stops" to this gradient. The stops go from 0 (beginning of the virtual line defined just above), to 1 (end of the virtual line). A color associated with a value of 0.5 will be right in the middle of the virtual line.
Here is an example that corresponds to an interpolated version of the French flag, going from blue to white, then to red, with proportional intervals. We define three colors, blue at position 0, white at position 0.5 and red at position 1:
grdFrenchFlag.addColorStop(0, "blue");
grdFrenchFlag.addColorStop(0.5, "white");
grdFrenchFlag.addColorStop(1, "red");
First, let's set the fillStyle or strokeStyle of the context with this gradient, then let's draw some shapes "on top of the gradient".
In our example, the gradient corresponds to an invisible rectangle that fills the canvas. If we draw a rectangle of the canvas size, it should be filled with the entire gradient:
ctx.fillStyle = grdFrenchFlag;
ctx.fillRect(0, 0, 300, 200);
The result is shown in the above pen: a big rectangle that fills the whole canvas, with colors going from blue (left) to white (middle) to red (right).
If you modify the source code that defines the direction of the gradient as follows...
grdFrenchFlag = ctx.createLinearGradient(0, 0, 300, 200);
... then you will define a gradient that goes from the top left corner of the canvas to the bottom right of the canvas. Let's see what it does:
Instead of drawing a filled rectangle that covers the whole surface of the canvas, let's draw several smaller rectangles:
Note that the canvas has its default background color where we did not draw anything. Where we have drawn rectangles, we have "masked" the background with colors from the color gradient.
Here is the code that draws the checkboard:
ctx.fillStyle = grdFrenchFlag;
ctx.fillRect(0, 0, 50, 50);
ctx.fillRect(100, 0, 50, 50);
ctx.fillRect(200, 0, 50, 50);
ctx.fillRect(50, 50, 50, 50);
ctx.fillRect(150, 50, 50, 50);
ctx.fillRect(250, 50, 50, 50);
ctx.fillRect(0, 100, 50, 50);
ctx.fillRect(100, 100, 50, 50);
ctx.fillRect(200, 100, 50, 50);
ctx.fillRect(50, 150, 50, 50);
ctx.fillRect(150, 150, 50, 50);
ctx.fillRect(250, 150, 50, 50);
This code is rather ugly isn't it? It would have been better to use a loop...
Here is function that draws a checkboard:
// n = number of cells per row/column
function drawCheckboard(n) {
ctx.fillStyle = grdFrenchFlag;
var l = canvas.width;
var h = canvas.height;
var cellWidth = l / n;
var cellHeight = h / n;
for(i = 0; i < n; i++) {
for(j = i % 2; j < n; j++) {
ctx.fillRect(cellWidth*i, cellHeight*j, cellWidth, cellHeight);
}
}
}
The two loops (lines 11-15) draw only one cell out of two (see the j = i % 2 at line 12). i is the column number and if the column is odd or even, either we draw or we do not draw a rectangle.
This code is much more complex than the previous one, taking 16 lines instead of 13, but is much more powerful. Try to call the function with a value of 10, 20, or 2...
Just as we used fillStyle and fillRect for drawing rectangles filled with a gradient, we can also use strokeStyle and strokeRect in order to draw wireframed rectangles. In the next example, which is just a variation of the previous one, we have used the lineWidth property to set the outline of the rectangles at 5 pixels:
Extract from source code:
function drawCheckboard(n) {
ctx.strokeStyle = grdFrenchFlag;
ctx.lineWidth=10;
...
for(i = 0; i < n; i++) {
for(j = i % 2; j < n; j++) {
ctx.strokeRect(cellWidth*i, cellHeight*j, cellWidth, cellHeight);
}
}
}
Let's go back to the very first example on this page - the one with the blue-white-red interpolated French flag. This time we will define a smaller gradient. Instead of going from (0, 0) to (300, 0), it will go from (100, 0) to (200, 0), while the canvas remains the same (width=300, height=200).
grdFrenchFlag = ctx.createLinearGradient(100, 0, 200, 0);
Like in the first example we will draw a filled rectangle that is the same size as the canvas:
We notice that "before" the gradient starts, the first color of the gradient is repeated without any interpolation (columns 0-100 are all blue), then we "see through" and the gradient is drawn (columns 100-200), then the last color of the gradient is repeated without any interpolation (columns 200-300 are red).
Nothing special; we will "see through the drawn shapes", and the parts of the gradient that are located in the canvas area will be shown. You can try this example that defines a gradient twice the size of the canvas:
grdFrenchFlag = ctx.createLinearGradient(0, 0, 600, 400);
And if we draw the same rectangle with the canvas size, here is the result:
The red color is beyond the bottom right corner.... we see only the top left quarter of the gradient.
This time, we would like to draw the checkboard with the gradient in each cell. How can we do this with one single gradient?
We can't! At least we can't without recreating it for each cell!
It suffices to create a new gradient before drawing each filled rectangle, and set it with the starting and ending point of its direction/virtual line according to each rectangle's coordinates. Try this:
Extract from source code:
function setGradient(x, y, width, height) {
grdFrenchFlag = ctx.createLinearGradient(x, y, width, height);
grdFrenchFlag.addColorStop(0, "blue");
grdFrenchFlag.addColorStop(0.5, "white");
grdFrenchFlag.addColorStop(1, "red");
// set the new gradient to the current fillStyle
ctx.fillStyle = grdFrenchFlag;
}
// n = number of cells per row/column
function drawCheckboard(n) {
var l = canvas.width;
var h = canvas.height;
var cellWidth = l / n;
var cellHeight = h / n;
for(i = 0; i < n; i+=2) {
for(j = 0; j < n; j++) {
var x = cellWidth*(i+j%2);
var y = cellHeight*j;
setGradient(x, y, x+cellWidth, y+cellHeight);
ctx.fillRect(x, y, cellWidth, cellHeight);
}
}
}
We wrote a function setGradient(startX, startY, endX, endY) that creates a gradient and set the fillStyle context property so that any filled shape drawn will have this gradient.
In the drawCheckBoard(...) function we call it just before drawing rectangles. In this way, each rectangle is drawn using its own gradient.