Lesson 14 - 2D canvas context in JavaScript
In the previous lesson, Images and drawing on canvas using JavaScript, we focused on images and learned to work
with <canvas>
. In today's JavaScript tutorial, we're going to
look at the <canvas>
in more detail. We'll explore its
possibilities step by step.
Clearing rectangles
In the previous lesson, we were drawing rectangles. We introduced the
fillRect()
and strokeRect()
methods. There's also the
clearRect()
method which erases the contents of a canvas on the
given area. The parameters are the same as of the two methods mentioned above.
But what's the difference between clearRect()
and
fillRect()
when we set the fill color to white? The canvas is not
white by default, but transparent. So if we clear the canvas using
fillRect()
, we'll see a white area, but we will not see the canvas
background and what's under the canvas.
Rectangles in paths
The last way to draw a rectangle is to put it in a path. Although it might
seem unnecessary at first glance as we have the fillRect()
and
strokeRect()
methods available, it's an easy way to both fill a
rectangle and draw its border at the same time. We'll simply draw the rectangle
as a path, draw its contents and then draw the border.
context.translate(30, 0); context.beginPath(); context.rect(10, 10, 60, 60); context.closePath(); context.fillStyle = "red"; context.strokeStyle = "blue"; context.stroke(); context.fill();
The result:
Context transformation
A very important thing when processing an image is transformation. From time
to time, it's useful when the zero position is not zero. Perhaps the most basic
example is when we want to render a specific 10px wide frame around the canvas
which we wouldn't be able to style just in CSS. Of course, we can draw
everything to the x + 10
and y + 10
positions, but
it'd be much easier to use canvas transformations. This gets more interesting
with rotations and sometimes it's the only way to achieve the expected
result.
Storing the context
Before performing any manipulation on the context it's useful to store it
first. We could return it to the x - 10
and y - 10
position later, but that'd be unnecessary and we'd have to remember the values.
Therefore, the context has the save()
and restore()
methods, neither requires any parameters. The save()
method saves
the current transformations, and the restore()
method restores
them.
As an example, we're going to draw a red frame around an image. Although this is easier to implement with CSS, and we'll try it in JavaScript.
As first, we'll store the canvas, its context and the image, you can do this
yourself. We'll set the context's fill color to red, for example, and fill the
entire <canvas>
with it. Then we'll save the context.
context.fillStyle = "#f00"; context.fillRect(0, 0, 510, 340); context.save();
Context translation
We move the context by the translate()
method which accepts the
x
and` y` coordinates as its parameters. The new zero point will be
at these coordinates. We'll move the canvas by 10px on both the x and y
axes.
context.translate(10, 10);
Now, we'll draw the image at the [0;0] position and restore the canvas context.
context.drawImage(image, 0, 0); context.restore();
When we open the application now, we can notice that although we have drawn the image at [0;0], it's been drawn at [10;10]. That happened thanks to the translation.
Do not forget to return the context to the original state if you ever need to work with it again.
Context scaling
We can zoom the context in and out. There's the scale()
method
to do so, which accepts multiples of the current scale for the X and Y axes. For
zooming out, we enter a decimal number less than 1.
Rotation
The last operation is rotation. We often need to turn something a bit. E.g.
when we need to draw a diamond, there's nothing easier than to rotate the
context. Of course, we could recalculate the points, but it'd be unnecessarily
complicated. We rotate the context using the rotate()
method, which
accepts the angle in radians as a parameter.
context.save(); context.translate(100, 100); context.rotate(45 * Math.PI / 180); context.strokeRect(0, 0, 50, 50); context.restore();
The result:
Shadow
All the operations that draw something on canvas can be done with a shadow.
We use the shadowColor
property to set the shadow color. We shift
the shadow by shadowOffsetX
and shadowOffsetY
and
specify how blurry it should be by shadowBlur
.
context.shadowColor = "red"; context.shadowOffsetX = 6; context.shadowOffsetY = 3; context.shadowBlur = 10;
The result:
Color gradients
Occasionally, the image can be "improved" by color gradients. There are 2 types of gradients - linear and radial. Both can be set for both filling and stroking.
Linear gradients
Linear gradients are simple, the colors are changing gradually as we specify
it. We create it using the createLinearGradient()
method on the
context object and we pass 4 parameters to it. These are the x
and
y
coordinates for both the beginning and the end of the gradient.
The newly created object has the addStopColor()
method, which adds
the gradient's color. This method accepts the "stop" position in the range of
0
- 1
as the first parameter and the given color as
the second one.
let gradient = context.createLinearGradient(0, 0, 100, 0); gradient.addColorStop(0, "yellow"); gradient.addColorStop(0.2, "orange"); gradient.addColorStop(0.4, "pink"); gradient.addColorStop(0.6, "red"); gradient.addColorStop(0.8, "green"); gradient.addColorStop(1, "blue"); context.fillStyle = gradient; context.fillRect(0, 0, 100, 100); context.font = "19px Calibri" context.fillText("ICT.social", 0, 115);
The result:
Radial gradients
The radial or circular gradients are used in exactly the same way, they only
create a different effect. The method that creates it is
createRadialGradient()
and it accepts 6 parameters. The
x
and y
of the starting point, the radius and all this
again for the end point. The values are quite confusing. The first
x
and` y` position actually defines the center, the first radius is
the radius at which the gradient begins. If it's zero, the gradient will start
straight from the center. If there's any other value, the first color will be in
the center and it'll start to change not before the specified radius. If you
want to draw a regular circle, the fourth and fifth parameters will copy the
first and second ones. The sixth parameter specifies at which radius the
gradient stops, the rest will be only of the last color. We set the colors in
the same way.
Pattern fill
One of the other fill options (available for stroking as well) is the pattern
fill. We create it by creating an image pattern and setting it to the
fillStyle
(or strokeStyle
). We create the pattern
using the createPattern()
method, where we pass the image as the
first parameter and the way to repeat as the second one. To repeat the pattern
on both the X
and` Y` axes, we can leave an empty
string
.
let pattern = context.createPattern(image, ""); context.fillStyle = pattern;
The result:
We can also use another <canvas>
element as a pattern.
Working with pixels
Until now, pixels have been managed by JavaScript, but sometimes, it's useful for us to work with individual pixels.
Image data
The 2D canvas context provides the ImageData
object. We get it
using the getImageData()
method, to which we pass the coordinates
of the top left corner and the size of the area as parameters. This object will
have the data
array in which all the RGBA pixel values of the
selected area are available. The pixels are sorted sequentially, row by row,
left to right.
Let's create an application that inverts colors of an image. We'll store our canvas, context, and the image as we always do. On the canvas, we'll draw the image and get the image data from it.
context.drawImage(img, 0, 0); let imageData = context.getImageData(0, 0, canvas.width, canvas.height);
Now we'll iterate through all the colors. Each pixel in the array is
represented by 4 values, since there's a value for each of its components (R, G,
B, A). Therefore, the loop won't have the standard ++
increment,
but we'll increase the control variable by 4
.
for (let i = 0; i < imageData.data.length; i += 4) {
In the loop, we'll set a new color to the i
(R),
i + 1
(G), and i + 2
(B) values. In our case, we'll
subtract the color value from 255
, which is the maximum value. We
invert the values by it and achieve the negative effect. We'll not alter the
alpha channel.
imageData.data[i + 0] = 255 - imageData.data[i + 0]; imageData.data[i + 1] = 255 - imageData.data[i + 1]; imageData.data[i + 2] = 255 - imageData.data[i + 2];
Finally, we'll put the modified ImageData
into the image at the
[0;0] position using the putImageData()
method.
context.putImageData(imageData, 0, 0);
The result:
Now you should be able to control <canvas>
via JavaScript.
I recommend trying out the exercises. In the next lesson, Solved tasks for JavaScript lessons 13-14, we'll look at
timers and animations.
In the following exercise, Solved tasks for JavaScript lessons 13-14, we're gonna practice our knowledge from previous lessons.
Did you have a problem with anything? Download the sample application below and compare it with your project, you will find the error easily.
Download
By downloading the following file, you agree to the license terms
Downloaded 2x (231.72 kB)
Application includes source codes in language JavaScript