🏷️ #javascript #frontend #webdev

JavaScript Basics #4

In this article, we are going to discuss two separate topics in JavaScript, regular expressions and the canvas.

Regular expression #

The first topic we are going to discuss in this article is called regular expression. It is technically not a part of JavaScript, it’s a separate language that is built into JavaScript as well as other programming languages. Regular expression has a very awkward and cryptic syntax, but it is also very useful. It is widely used among programmers as a tool to describe, match and replace patterns in string data.

Creating a regular expression #

A regular expression is an object. There are two ways you can create a regular expression in JavaScript. You can either use a RegExp() constructor or you can enclose the pattern inside a pair of forward-slash (/) characters.

1
2
let re1 = new RegExp("abc");
let re2 = /abc/;

Both of these examples describe the same pattern: a character a followed by a b followed by a c. The second notation, however, treats backslash (\) characters differently. For example, since the forward-slash denotes the pattern, if you want a forward-slash to be a part of the pattern, you need to put a backslash in front of it (\/).

Matching patterns #

Regular expression offers a handful of methods for us to use, the most commonly used one should be the test() method, which is used for matching patterns in string data.

1
2
console.log(/abc/.test("abcde")); // β†’ true
console.log(/abc/.test("abxde")); // β†’ false

In this example, the test() method will examine the string that is passed to it, and return a boolean value telling you if a pattern match is found.

However, simply testing if the pattern "abc" is found in a string does not seem very useful. Sometimes we want to test for a match using a set of characters. For example, the following code test if at least one of the characters, from character 0 to character 9, exists in the string "in 1992".

1
2
3
4
console.log(/[0123456789]/.test("in 1992")); // β†’ true

// A hyphen character can be used to indicate a range of characters
console.log(/[0-9]/.test("in 1992")); // β†’ true

It is also possible to match any character that is not in the set. For example, this time we’ll match any character that is not 1 or 0.

1
2
3
4
5
let notBinary = /[^01]/;
console.log(notBinary.test("1100100010100110")); // β†’ false

// The string contains a character "2" which is not in the set [01]
console.log(notBinary.test("1100100010200110")); // β†’ true

Some of the commonly used character sets have shortcuts in regular expressions. For instance, \d represents all digit characters, same as [0-9].

  • \d Any digit character
  • \w Any alphanumeric character (word character)
  • \s Any whitespace character (space, tab, new line …)
  • \D Any non-digit character
  • \W Any non-alphanumeric character
  • \S Any non-whitespace character
  • . Any character except for the new line

Now, we could match a date-time format (10-07-2021 16:06) like this, and as you see, it is awkward and cryptic as advertised.

1
2
let dateTime = /\d\d-\d\d-\d\d\d\d \d\d:\d\d/;
console.log(dateTime.test("10-07-2021 16:06")); // β†’ true

Matching repeating patterns #

You may have noticed that in our previous example, each \d only matches one digit character. What if we want to match a sequence of digits of arbitrary length? We can do that by putting a plus mark (+) after the element we wish to repeat.

1
2
console.log(/\d+/.test("123")); // β†’ true
console.log(/\d+/.test("")); // β†’ false

The star sign has a similar meaning except it allows the element to match for zero times.

1
2
console.log(/\d*/.test("123")); // β†’ true
console.log(/\d*/.test("")); // β†’ true

We can also indicate precisely how many times we want the element to repeat. For example, if we put {4} after an element, that means this element will be repeated four times. If we put {2,4} after that element, it means the element will be repeated at least twice and at most four times.

1
2
console.log(/\d{3}/.test("123")); // β†’ true
console.log(/\d{3}/.test("12")); // β†’ false

It is possible to repeat a group of elements as well. We only need to enclose that group of elements inside a pair of parentheses.

1
2
let cartoonCrying = /boo+(hoo+)+/i;
console.log(cartoonCrying.test("Boohoooohoohooo")); // β†’ true

In some cases, we need a part of the pattern to be optional. For example, the word “neighbour” can also be spelled “neighbor”, which means the character “u” should be optional. Here is what we can do:

1
2
3
4
5
let neighbor = /neighbou?r/;
console.log(neighbor.test("neighbour"));
// β†’ true
console.log(neighbor.test("neighbor"));
// β†’ true

Other methods for matching patterns #

The test() method is the simplest way of finding out if a pattern match is found in a string. However, it doesn’t give you much information besides returning a boolean value telling you if a match is found. The regular expression also has an exec() method (exec stands for execute) that would return an object giving you more information, such as what the match is and where it is found.

1
2
3
4
5
let match = /\d+/.exec("one two 100");
console.log(match); // β†’ ["100"]

// The index property tells you where in the string the match begins
console.log(match.index); // β†’ 8

There is also a match() method that belongs to the string type, which behaves similarly.

1
console.log("one two 100".match(/\d+/)); // β†’ ["100"]

The exec() method can be very useful in practice. For example, we can extract a date and time from a string like this:

1
let [_, month, day, year] = /(\d{1,2})-(\d{1,2})-(\d{4})/.exec("1-30-2021");

The underscore (_) is ignored, it is used to skip the full match that is returned by the exec() method.

However, now we have another problem from this example. If we pass to the exec() method a sequence of nonsense like "100-1-3000", it would still happily extract a date from it.

In this case, we must enforce that the match must span the entire string. To do that, we use the boundary markers ^ and $. The caret sign (^) marks the start of the string and the dollar sign ($) matches the end of the string. So, for instance, the pattern /^\d$/ would match a string that only consists of one digit character.

Sometimes you don’t want the match to be the entire string, a string could be a complete sentence and you want the match to be a word in that sentence. To mark a word boundary, we use the \b marker.

1
2
console.log(/cat/.test("concatenate")); // β†’ true
console.log(/\bcat\b/.test("concatenate")); // β†’ false

The last type of pattern I’d like to introduce before we wrap up this topic is the choice pattern. Sometimes we don’t want to match a specific pattern, but instead, we have a list of acceptable patterns. we can divide the different patterns using the pipe character (|).

1
2
3
let animalCount = /\b\d+ (pig|cow|chicken)s?\b/;
console.log(animalCount.test("15 pigs")); // β†’ true
console.log(animalCount.test("15 pigchickens")); // β†’ false

If you are interested in learning more about the regular expression, this is a very interesting website to play around.

Replacing a pattern #

Besides the match() method, string values also have a replace() method that replaces part of the string with another string.

1
2
console.log("papa".replace("p", "m"));
// β†’ mapa

The first argument of the replace() method can also be a regular expression, in which case the first match of that regular expression will be replaced with the second argument. If you wish to replace all matches of the regular expression, add a g option (global option) to that regular expression.

1
2
console.log("Borobudur".replace(/[ou]/, "a")); // β†’ Barobudur
console.log("Borobudur".replace(/[ou]/g, "a")); // β†’ Barabadar

The canvas #

Remember when we talked about HTML and CSS, we briefly introduced something called SVG? It allows us to create beautiful images by simply using HTML tags. Today, we are going to introduce something similar called canvas, except it allows us to use javascript to create graphics on web pages. And because it uses a programming language instead of a simple markup language, that makes canvas much more flexible and powerful compared to SVG.

We know that the SVG has a DOM tree structure, and the shape, color, and position are all represented using HTML tags. The canvas, however, is one single HTML node, but it encapsulates a space on the web page, where you can create beautiful artworks using JavaScript. This space can be defined using the <canvas> tag. Here is an example where we create a simple rectangle inside the canvas space:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<canvas width="300px" height="200px"></canvas>
<script>
  let canvas = document.querySelector("canvas");
  let context = canvas.getContext("2d");

  // Define the colour of the rectangle
  context.fillStyle = "red";

  // The first two parameters means that the top left corner of the ractagle is at coordinate (10, 10)
  // The last two parameters define the width and height of the ractangle (width:100px, height:50px)
  context.fillRect(10, 10, 100, 50);
</script>

The getContext() method is used to access the drawing interface, which is like a toolbox where your digital pens and pencils are stored. The parameter "2d" stands for two-dimensional graphics. If you are interested in creating three-dimensional graphics, you should use WebGL instead. But we are only focusing on the 2D system for now.

Also, notice that we defined the size of the canvas at the beginning. If you don’t do that, the canvas element will take a default width of 300 pixels and a height of 150 pixels.

Lines #

The rectangle we just created is solid, the inside of the rectangle is filled. What if we want something different? It is also possible for us to create a rectangle that only has an outline, by using a very similar method, strokeRect(). This method also takes four parameters, the first two define the position and the last two define the size.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<canvas></canvas>
<script>
  let canvas = document.querySelector("canvas");
  let context = canvas.getContext("2d");

  // Define the color, position and size
  context.strokeStyle = "blue";
  context.strokeRect(10, 10, 100, 50);

  // Define the width of the strok and create a new rectangle
  context.lineWidth = 5;
  context.strokeRect(150, 10, 100, 50);
</script>

Canvas Line Rectangle

Paths #

Now you might be wondering, that’s not so exciting, we can create rectangles using SVGs just as easily. Don’t worry, the real power of the canvas starts now.

First, we need to understand what a path is. A path is a sequence of line segments. For example, we have a line that starts from coordinate (0, 0) to (0, 50), the second line from (0, 50) to (80, 50), and the third line from (80, 50) to (80, 100). These three line segments will form a path.

The canvas allows us to do something like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<canvas width="500px" height="500px"></canvas>
<script>
  let canvas = document.querySelector("canvas");
  let context = canvas.getContext("2d");

  context.lineWidth = 5;
  context.strokeStyle = "green";

  context.beginPath();

  // The path starts at (10, 10)
  context.moveTo(10, 10);

  // Drawing the path: (10, 10) -> (150, 10) -> (150, 150) -> (10, 150) -> (10,10)
  context.lineTo(150, 10);
  context.lineTo(150, 150);
  context.lineTo(10, 150);
  context.lineTo(10, 10);

  context.stroke();
</script>

With paths, we can create any shape we want. For example, the following code creates a triangle:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<canvas width="500px" height="500px"></canvas>
<script>
  let canvas = document.querySelector("canvas");
  let context = canvas.getContext("2d");

  context.beginPath();

  context.fillStyle = "red";

  context.moveTo(200, 10);
  context.lineTo(250, 100);
  context.lineTo(150, 100);
  context.lineTo(200, 10);

  context.fill();
</script>

canvas triangle

Curves #

A path could be formed by straight lines, and it could also be formed by curves. A curve, however, is a little bit more difficult to define. To define a curve, we need a start point, a destination point, and a control point. The curve will not go through the control point directly, but instead, it defines a point where the tangent line of the start and destination point goes through.

This is a little hard to understand. I suggest you get familiar with the pen tool in Photoshop or the path tool in GIMP first. They share the same concept, except when you are coding, you need to imagine what the curve looks like.

Here is another example. We’ll first draw the curve, and then draw the tangent lines and the control point, so that it helps you understand what’s going on here:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<canvas width="500px" height="500px"></canvas>
<script>
  let canvas = document.querySelector("canvas");
  let context = canvas.getContext("2d");

  context.beginPath();

  // start point = (10, 90)
  context.moveTo(10, 90);

  // control point = (60,10); destination point = (90,90)
  context.quadraticCurveTo(60, 10, 90, 90);

  // destination point tangent
  context.lineTo(60, 10);

  // start point tangent
  context.moveTo(10, 90);
  context.lineTo(60, 10);

  context.closePath();
  context.stroke();
</script>

Curve

Sometimes we want the start point tangent and the destination point to have different control points. That is also possible to achieve using the bezierCurveTo() method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<canvas width="500px" height="500px"></canvas>
<script>
  let canvas = document.querySelector("canvas");
  let context = canvas.getContext("2d");

  context.beginPath();

  // start point = (10, 90)
  context.moveTo(10, 90);

  // start control point = (60,10); destination control point = (30,80); destination point = (90,90)
  context.bezierCurveTo(60, 10, 30, 80, 90, 90);

  // destination point tangent
  context.lineTo(30, 80);

  // start point tangent
  context.moveTo(10, 90);
  context.lineTo(60, 10);

  context.closePath();
  context.stroke();
</script>

Curve with two control point

Texts #

Texts might also be useful when we are creating graphs. We can draw texts using either fillTextΒ andΒ strokeText. The latter will only render the outline of the texts instead of filling it.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<canvas width="1500px" height="500px"></canvas>
<script>
  let canvas = document.querySelector("canvas");
  let context = canvas.getContext("2d");

  context.font = "28px Georgia";

  context.fillText(
    "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
    10,
    50,
  );

  context.strokeText(
    "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
    10,
    100,
  );
</script>

Texts in Cnavas

The last two parameters indicate the position of the text, but unlike drawing shapes, it defines the coordinate of the start of the text’s baseline. The baseline is the line that the text stands on.

Transformations #

There are primarily three types of transformations, translate(), scale() and rotate(). Remember that these methods need to be put before the graph you wish to transform.

translation() will move the graph from one position to another:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<canvas width="1500px" height="1500px"></canvas>
<script>
  let canvas = document.querySelector("canvas");
  let context = canvas.getContext("2d");

  // Move whatever graph created after to the right for 50px and downward for 100px
  context.translate(50, 100);

  // Create a graph
  context.beginPath();

  context.fillStyle = "red";

  context.moveTo(200, 10);
  context.lineTo(250, 100);
  context.lineTo(150, 100);
  context.lineTo(200, 10);

  context.fill();
</script>

The scale() will make the original graph bigger or smaller:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<script>
  let canvas = document.querySelector("canvas");
  let context = canvas.getContext("2d");

  // Make the graph 2 times wider (along x-axis) 0.5 time shorter (along y-axis)
  context.scale(2, 1/2);

  // Create a graph
  ...
</script>

And finally, rotate() can rotate the graph along an axis:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<canvas width="1500px" height="1500px"></canvas>
<script>
  let canvas = document.querySelector("canvas");
  let context = canvas.getContext("2d");

  // Rotate the graph clockwise for 18 degrees. Notice that the rotate() method takes radian instead of degree.
  context.rotate(0.1 * Math.PI);

  // Create a graph
  ...
</script>

Bitmap graphics #

In computer graphics, there is something called vector graphics and bitmap graphics. All the graphs we’ve been talking about so far are vector graphics. Their primary difference is that the bitmap graphics are formed by pixels while the vector graphics are not. Instead, they are formed by paths, with a direction and a magnitude (length), like a vector.

However, it is necessary for us sometimes to insert some bitmap graphics in our vector graphic design. We can do that by using the drawImage() method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<canvas width="1500px" height="1500px"></canvas>
<script>
  let canvas = document.querySelector("canvas");
  let context = canvas.getContext("2d");

  let img = document.createElement("img");
  img.src = "cat.jpg";

  img.addEventListener("load", () => {
    context.drawImage(img, 10, 10, 360, 240);
  });
</script>

In this example, the image will be drawn at the coordinate (10, 10), with the size 360px * 240px.

We need to add the event listener because, without it, the image will load after the canvas, so we have to make the canvas wait for the image to load first.


If you think my articles are helpful, please consider making a donation to me. Your support is greatly appreciated.

Subscribe to my newsletter ➑️

βœ… News and tutorials every other Monday

βœ… Unsubscribe anytime

βœ… No spam. Always free.