How to Build a Drawing Board with JavaScript

In this lesson, we are going to try something more challenging. Previously, when we discussed pointer events, we mentioned that it is possible to access the pointer coordinates using the clientX and clientY properties in the event object.

javascript
1let button = document.getElementById("button");
2
3button.addEventListener("click", (event) => {
4  console.log("X: " + event.clientX);
5  console.log("Y: " + event.clientY);
6});

Also, recall that you can find out which mouse button is clicked by accessing the button property.

javascript
1button.addEventListener("click", (event) => {
2  if (event.button == 0) {
3    console.log("Left button");
4  } else if (event.button == 1) {
5    console.log("Middle button");
6  } else if (event.button == 2) {
7    console.log("Right button");
8  }
9});

Combining these concepts, you can create a simple drawing board that creates a star when you click on the page using the primary button, and clicking on the star with the secondary button will remove it.

Preparations

html
1<!doctype html>
2<html lang="en">
3  <head>
4    <meta charset="UTF-8" />
5    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6    <title>Drawing Board</title>
7  </head>
8
9  <body>
10    <svg id="drawingBoard" width="100%" height="500"></svg>
11
12    <script src="index.js"></script>
13  </body>
14</html>

Let's start with the HTML code. You only need one <svg> element in this case, and make sure to set its id to drawingBoard.

Drawing stars

Next, you need a function that draws the star. The idea is to create <polygon> elements with different point attributes and appended as <svg>'s child when the function is executed.

javascript
1const svg = document.getElementById("drawingBoard");
javascript
1function drawStar(x, y) {
2  const size = 30;
3  const spikes = 5;
4
5  const star = document.createElementNS(
6    "http://www.w3.org/2000/svg",
7    "polygon"
8  );
9
10  const points = [x + size * Math.cos(0), y + size * Math.sin(0)];
11  for (let i = 1; i < spikes * 2; i++) {
12    const angle = (i * Math.PI) / spikes;
13    const factor = i % 2 === 0 ? size : size / 2;
14    points.push(x + factor * Math.cos(angle), y + factor * Math.sin(angle));
15  }
16
17  star.setAttribute("points", points.join(" "));
18  star.setAttribute("fill", "black");
19
20  svg.appendChild(star);
21}

This example seems very complicated, but don't worry, let's analyze it line by line.

First of all, we initialized two variables, size and spikes. size define the size of the star, and spikes define the number of spikes of the star. Later, we'll need them to calculate the points required for the <polygon> element.

The createElementNS() method is similar to the createElement() method we've discussed before, except it is used to create new XML elements under a certain namespace. NS stands for namespace.

The next part is the most confusing:

javascript
1const points = [x + size * Math.cos(0), y + size * Math.sin(0)];
2for (let i = 1; i < spikes * 2; i++) {
3  const angle = (i * Math.PI) / spikes;
4  const factor = i % 2 === 0 ? size : size / 2;
5  points.push(x + factor * Math.cos(angle), y + factor * Math.sin(angle));
6}

The variable points refers to the points of the star. In this case, we are creating a star with five spikes, which means there should be ten points, five outer points, and five inner points. The first point is initialized as:

javascript
1[x + size * Math.cos(0), y + size * Math.sin(0)];

x and y are arguments passed to the drawStar() function, and they refer to the coordinate of the position where the mousedown event happened.

And then, the loop calculates the coordinates of the other points based on the initial point, the number of spikes, and the size.

First, we need to know the angle from the number of spikes:

javascript
1const angle = (i * Math.PI) / spikes;

Here, we assume the points are distributed evenly around the circumference of a full circle (360 degrees, or simply Math.PI).

Then there is the factor, which should equal either size or size / 2, depending on whether the point is an inner or outer point. In this case, if i is even, the factor would equal size, which makes the point an outer point. If i is odd, the factor equals size / 2, and the point will be an inner point.

Lastly, push() method pushes the new coordinates to the points array. x + factor * Math.cos(angle) calculates the X coordinate and y + factor * Math.sin(angle) gives the Y coordinate.

The remaining part of the drawStar() function should be easy to understand, setAttribute() creates new attributes points and fill, appendChild() appends the new <polygon> element as a child of <svg>.

Removing stars

javascript
1function removeStar(x, y) {
2  const elements = document.elementsFromPoint(x, y);
3
4  for (let element of elements) {
5    if (
6      element.tagName === "polygon" &&
7      element.getAttribute("fill") === "black"
8    ) {
9      element.remove();
10      break;
11    }
12  }
13}

elementsFromPoint() is an element selector we haven't yet discussed. It returns a collection of elements based on the given coordinate. You can access the coordinate from the event object and then pass it to the elementsFromPoint() function.

The returned collection can then be iterated over using the for of loop we've discussed before.

Setting up event handlers

javascript
1svg.addEventListener("mousedown", (event) => {
2  if (event.button === 0) {
3    drawStar(event.clientX, event.clientY);
4  } else if (event.button === 2) {
5    removeStar(event.clientX, event.clientY);
6  }
7});
8
9svg.addEventListener("contextmenu", (event) => {
10  event.preventDefault();
11});

Finally, you'll need to set up two different event handlers:

  • The contextmenu event handler is used to prevent the default action of a right click.
  • The mousedown event handler checks which mouse button is used, and then call the corresponding function, either drawStar() or removeStar().