Methods to create a canvas drawing instrument with vanilla JavaScript

Methods to create a canvas drawing instrument with vanilla JavaScript
Methods to create a canvas drawing instrument with vanilla JavaScript


By the tip of this tutorial, it is possible for you to to attract totally different shapes of various colours. Check out the ultimate working demo beneath. Be at liberty to fork and play around with it!

HTML construction

The HTML Construction will encompass the next parts:

  • A <choose> factor that can have 4 drop-down choices, specifically; freehand, circle, rectangle and eraser
  • An <enter kind="coloration"> which is able to outline a coloration picker.
  • A  <canvas> factor which will likely be  draw with JavaScript.

Right here is the HTML construction (as ever, to simplify the method) utilizing Bootstrap.

1
<div class="container d-flex justify-content-center align-items-center mt-5">
2
  <div class="row">
3
    <h1>Drawing Device</h1>
4
  </div>
5
</div>
6
<div class="container d-flex justify-content-center align-items-center">
7
  <div class="row mt-5">
8
    <div class="col-md-4 col-sm-6 col-8">
9
      <div class="mb-3 d-flex justify-content-center align-items-center">
10
        <label for="instrument">Device:</label>
11
        <choose id="instrument" class="form-control">
12
          <choice worth="rectangle">Rectangle</choice>
13
          <choice worth="freehand">Freehand</choice>
14
          <choice worth="circle">Circle</choice>
15
          <choice worth="eraser">Eraser</choice>
16
        </choose>
17
      </div>
18
      <div class="mb-3 d-flex justify-content-center align-items-center">
19
        <label for="drawcolor">Shade:</label>
20
        <enter kind="coloration" id="drawcolor" title="drawcolor" worth="#00FFFF" class="form-control" />
21
      </div>
22
    </div>
23
    <div class="col-md-8">
24
      <canvas width="600" peak="450"></canvas>
25
    </div>
26
  </div>
27
</div>

For the canvas, we’re setting a customized width and peak with <canvas width="600" peak="450"></canvas>.

The customized setting defines the dimensions and also will be certain that the drawing space is appropriately scaled and permits for exact management over the scale of the canvas.

Styling with CSS

Add the next customized types:

1
@import url("https://fonts.googleapis.com/css2?household=DM+Mono:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&show=swap");
2
physique {
3
  background-color: rgb(247, 248, 249);
4
   font-family: "DM Mono", monospace;
5
}
6
canvas {
7
  border: 1px strong rgb(33, 32, 32);
8
  border-radius: 10px;
9
  background-color: #fff;
10
  cursor: crosshair;
11
}
12
h1{
13
  font-weight:600;
14
}

The customized types function a customized Google font, a border,  a border radius to the canvas factor, and a white background coloration.

JavaScript performance

Begin by getting the canvas factor

1
const canvas = doc.querySelector("canvas");

Subsequent, create a 2D context object which is able to enable us to attract within the canvas. The 2D context object accommodates strategies for drawing on the canvas.

1
ctx = canvas.getContext("2nd", { willReadFrequently: true });

Outline some preliminary variables:

1
let isDrawing = false;
2
let startX = 0;
3
let startY = 0;
4
let initialImageData;

  • isDrawing : this variable will maintain monitor of when the canvas is being drawn on.
  • startX is the preliminary level on the X axis on the canvas the place any drawing will begin.
  • startY is the preliminary level on the y axis  on the canvas the place any drawing will begin.
  • initialImageData is used to make a copy of how the canvas regarded earlier than any drawing begins. It is also helpful for stopping trails when new shapes are drawn.

Add occasion listeners to get the chosen coloration and gear:

1
selectTool = doc.getElementById("instrument");
2

3
let currentTool = selectTool.worth;
4

5
selectedColor = doc.getElementById("drawcolor");
6
let coloration = selectedColor.worth;
7

8
selectedColor.addEventListener("enter", () => {
9
  coloration = selectedColor.worth;
10
});
11

12
selectTool.addEventListener("change", () => {
13
  currentTool = selectTool.worth;
14
});

Subsequent, add an occasion listener to the canvas on the mousedown occasion. The mousedown occasion will invoke the startDrawing() operate.

1
canvas.addEventListener("mousedown", startDrawing);

Create the referred to as startDrawing() operate which is able to  appear to be this:

1
operate startDrawing(e) {
2
  ctx.lineWidth = 5;
3
  startX = e.offsetX;
4
  startY = e.offsetY;
5
  isDrawing = true;
6
  ctx.beginPath();
7

8
  ctx.fillStyle = coloration;
9
  ctx.strokeStyle = coloration;
10
  initialImageData = ctx.getImageData(0, 0, canvas.width, canvas.peak);
11
}

Within the code above, on the mousedown occasion , we are going to use the linewidth() technique supplied by the 2D context object to set a customized measurement for the drawing line width in pixels.

  • isDrawing = true; units the IsDrawing worth to true to suggest that the drawing has began.
  • startX = e.offsetX; will set the worth of startX to the x-coordinate of the mouse pointer.
  • startY = e.offsetY; will set the worth of startY to the y-cordinate of the mouse pointer.
  • ctx.beginPath(); beginPath () is a 2D context technique which begins a brand new path. On this case, a brand new path will likely be began on the intersection of startX and startY.
  • ctx.fillStyle = coloration; will set the colour used to fill the drawing
  • ctx.strokeStyle = coloration; units the chosen coloration because the stroke coloration.

Subsequent, add an occasion listener to the canvas on the mousemove occasion. The mousemove occasion will invoke the Drawing() operate.

1
canvas.addEventListener("mousemove", drawing);

When the consumer strikes the mouse, create a operate referred to as drawing which is able to first examine for the isDrawing situation, if its not true, the operate will exit.

1
operate drawing(e) {
2
  if (!isDrawing) return;
3
  }

If the isDrawing situation is occurring, we are going to use situations to examine the present chosen instrument and replace appropriately.  We’ll create case change statements for every of the next instruments: 

  • freehand
  • circle
  • triangle
  • eraser
1
operate drawing(e) {
2
  if (!isDrawing) return;
3

4

5
  change (currentTool) {
6
    case "freehand":
7
      //  use  freehand instrument
8
      break;
9

10
    case "rectangle":
11
      //  draw rectangle
12
      break;
13

14
    case "circle":
15
      //  draw circle
16
      break;
17

18
    case "eraser":
19
      //erase
20
      break;
21

22
    default:
23
      break;
24
  }
25
}

For the freehand instrument, replace the operate as proven beneath:

1
 operate drawing(e) {
2
    if (!isDrawing) return;  
3
    change (currentTool) {
4
      case "freehand":
5
        ctx.moveTo(startX, startY);
6
        ctx.lineTo(e.offsetX, e.offsetY);
7
        ctx.stroke();
8
        startX = e.offsetX;
9
        startY = e.offsetY;
10
        break;
11
        // remainder of the code
12
}}

When the freehand instrument is chosen, we are going to do the next:

  • ctx.moveTo(startX, startY);  will transfer the drawing cursor to the place to begin
  • ctx.lineTo(e.offsetX, e.offsetY); will add a line from the place to begin to the present mouse place
  • ctx.stroke(); will draw the road path with the chosen coloration.
  • startX = e.offsetX;  and startY = e.offsetY; will reset the beginning factors.

When the rectangle is chosen, replace the operate as follows:

1
 operate drawing(e) {
2
    if (!isDrawing) return;
3
    ctx.putImageData(initialImageData, 0, 0);
4
  
5
    change (currentTool) {
6
      case "freehand":
7
        ctx.moveTo(startX, startY);
8
        ctx.lineTo(e.offsetX, e.offsetY);
9
        ctx.stroke();
10
        startX = e.offsetX;
11
        startY = e.offsetY;
12
        break;
13
  
14
      case "rectangle":
15
        const width = e.offsetX - startX;
16
        const peak = e.offsetY - startY;
17
        ctx.fillRect(startX, startY, width, peak);
18
        ctx.beginPath();
19
        break;
20
}}

  • const width = e.offsetX - startX; The width is obtained by the distinction between  the beginning place, represented by startX and the currrent x-cordinate of the mouse pointer. 
  • const peak = e.offsetY - startY; To get the peak, we get the distinction between the beginning place, represented by startY and the currrent y-cordinate of the mouse pointer. 
  • ctx.fillRect(startX, startY, width, peak); the fillRect() technique will draw a crammed rectangle. This technique takes in parameters within the order supplied.

 

To attract a circle, we first have to get the circle’s radius, we are going to  then use the .arc() technique to attract a curve to the desired path. The .arc() technique has the next syntax.

1
context.arc(x, y, r, startAngle, endAngle, counterclockwise)

the place

  • x and y are the x and y-coordinate of the middle of the circle
  • r is the radius of the circle, which is calculated by the gap from the middle to any level within the circumference of the circle. To get the radius of the circle, we are going to use the Pythagoras theorem
  • startAngle is the angle at which the trail begins, measured in radians. In the context of a circle, that is sometimes set to 0, indicating the place to begin of the trail
  • endAngle is the angle at which the trail ends in radians. It’s obtained by 2*PI (corresponds to 360 levels)

Let’s get the radius utilizing the Pythagoras theorem.

1
const radius = Math.sqrt(
2
        (e.offsetX - startX) ** 2 + (e.offsetY - startY) ** 2
3
      );

Now if we substitute our values within the .arc() technique, the code for drawing a circle will appear to be this:

1
operate drawing(e) {
2
  if (!isDrawing) return;
3
  ctx.putImageData(initialImageData, 0, 0);
4

5
  change (currentTool) {
6
    case "freehand":
7
      ctx.moveTo(startX, startY);
8
      ctx.lineTo(e.offsetX, e.offsetY);
9
      ctx.stroke();
10
      startX = e.offsetX;
11
      startY = e.offsetY;
12
      break;
13

14
    case "rectangle":
15
      const width = e.offsetX - startX;
16
      const peak = e.offsetY - startY;
17
      ctx.fillRect(startX, startY, width, peak);
18
      ctx.beginPath();
19
      break;
20

21
    case "circle":
22
      const radius = Math.sqrt(
23
        (e.offsetX - startX) ** 2 + (e.offsetY - startY) ** 2
24
      );
25
      ctx.beginPath();
26
      ctx.arc(startX, startY, radius, 0, 2 * Math.PI);
27
      ctx.fill();
28
      ctx.stroke();
29
      break;
30

31
  
32
  }
33
}

Lastly, for the eraser instrument, replace the operate as follows:

1
operate drawing(e) {
2
  if (!isDrawing) return;
3
  ctx.putImageData(initialImageData, 0, 0);
4

5
  change (currentTool) {
6
    case "freehand":
7
      ctx.moveTo(startX, startY);
8
      ctx.lineTo(e.offsetX, e.offsetY);
9
      ctx.stroke();
10
      startX = e.offsetX;
11
      startY = e.offsetY;
12
      break;
13

14
    case "rectangle":
15
      const width = e.offsetX - startX;
16
      const peak = e.offsetY - startY;
17
      ctx.fillRect(startX, startY, width, peak);
18
      ctx.beginPath();
19
      break;
20

21
    case "circle":
22
      const radius = Math.sqrt(
23
        (e.offsetX - startX) ** 2 + (e.offsetY - startY) ** 2
24
      );
25
      ctx.beginPath();
26
      ctx.arc(startX, startY, radius, 0, 2 * Math.PI);
27
      ctx.fill();
28
      ctx.stroke();
29
      break;
30

31
    case "eraser":
32
      ctx.strokeStyle = "#fff";
33
      ctx.moveTo(startX, startY);
34
      ctx.lineTo(e.offsetX, e.offsetY);
35
      ctx.stroke();
36
      startX = e.offsetX;
37
      startY = e.offsetY;
38
      break;
39

40
    default:
41
      break;
42
  }
43
}

The erase performance is just like the freehand instrument, besides that it makes use of the colour white to cowl any earlier colours.

The final performance is the stopDrawing() operate  which occurs on mouseup occasion which is able to appear to be this;

1
canvas.addEventListener("mouseup", stopDrawing);
2
operate stopDrawing(e) {
3
  isDrawing = false;
4
  ctx.closePath();
5
}

On the mouseup occasion, drawing ought to cease and the present path ought to be closed. That is to make sure that no additional drawing operations happen till a brand new mousedown occasion happens.

The ctx.closePath() technique is used to shut the present path, guaranteeing that the form being drawn is finalized.

Closing demo

Let’s remind ourselves what now we have constructed! Right here is the demo:

Conclusion

This tutorial has lined easy methods to create a drawing app with Vanilla JavaScript. You’ll be able to additional improve this app by including options comparable to the flexibility to save lots of drawings, customized brush sizes, totally different shapes and instruments, and so forth.

Leave a Reply

Your email address will not be published. Required fields are marked *