Como criar uma ferramenta de desenho em tela com JavaScript vanilla

Ao final deste tutorial, você será capaz de desenhar diferentes formas de cores diferentes. Dê uma olhada na demonstração final de trabalho abaixo. Sinta-se à vontade para bifurcar e brincar com ela!

Estrutura HTML

A estrutura HTML consistirá nos seguintes componentes:

  • UM que definirá um seletor de cores.
  • UM elemento que será desenhado com JavaScript.

Aqui está a estrutura HTML (como sempre, para simplificar o processo) usando Bootstrap.

1
 class="container d-flex justify-content-center align-items-center mt-5">
2
   class="row">
3
    

Drawing Tool

4
  
5

6
 class="container d-flex justify-content-center align-items-center">
7
   class="row mt-5">
8
     class="col-md-4 col-sm-6 col-8">
9
       class="mb-3 d-flex justify-content-center align-items-center">
10
         for="tool">Tool:
11
         id="tool" class="form-control">
12
           value="rectangle">Rectangle
13
           value="freehand">Freehand
14
           value="circle">Circle
15
           value="eraser">Eraser
16
        
17
      
18
       class="mb-3 d-flex justify-content-center align-items-center">
19
         for="drawcolor">Color:
20
         type="color" id="drawcolor" name="drawcolor" value="#00FFFF" class="form-control" />
21
      
22
    
23
     class="col-md-8">
24
       width="600" height="450">
25
    
26
  
27

Para a tela, estamos definindo uma largura e altura personalizadas com .

A configuração personalizada define o tamanho e também garante que a área de desenho seja dimensionada adequadamente e permita um controle preciso sobre as dimensões da tela.

Estilização com CSS

Adicione os seguintes estilos personalizados:

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

Os estilos personalizados apresentam uma fonte personalizada do Google, uma borda, um raio de borda para o elemento de tela e uma cor de fundo branca.

Funcionalidade JavaScript

Comece obtendo o elemento canvas

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

Em seguida, crie um objeto de contexto 2D que nos permitirá desenhar na tela. O objeto de contexto 2D contém métodos para desenhar na tela.

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

Defina algumas variáveis ​​iniciais:

1
let isDrawing = false;
2
let startX = 0;
3
let startY = 0;
4
let initialImageData;
  • isDrawing : esta variável monitorará quando a tela está sendo desenhada.
  • startX é o ponto inicial no eixo X da tela onde qualquer desenho começará.
  • startY é o ponto inicial no eixo y da tela onde qualquer desenho começará.
  • initialImageData é usado para manter uma cópia de como a tela parecia antes de qualquer desenho começar. Também é útil para evitar rastros quando novas formas são desenhadas.

Adicione ouvintes de eventos para obter a cor e a ferramenta selecionadas:

1
selectTool = document.getElementById("tool");
2

3
let currentTool = selectTool.value;
4

5
selectedColor = document.getElementById("drawcolor");
6
let color = selectedColor.value;
7

8
selectedColor.addEventListener("input", () => {
9
  color = selectedColor.value;
10
});
11

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

Em seguida, adicione um ouvinte de eventos à tela no mousedown evento. O mousedown evento invocará o startDrawing() função.

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

Crie o chamado startDrawing() função que ficará assim:

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

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

No código acima, no mousedown evento, usaremos o linewidth() método fornecido pelo objeto de contexto 2D para definir um tamanho personalizado para a largura da linha de desenho em pixels.

  • isDrawing = true; define o IsDrawing valor como verdadeiro para indicar que o desenho foi iniciado.
  • startX = e.offsetX; definirá o valor de startX para a coordenada x do ponteiro do mouse.
  • startY = e.offsetY; definirá o valor de startY para a coordenada y do ponteiro do mouse.
  • ctx.beginPath(); beginPath () é um método de contexto 2D que inicia um novo caminho. Neste caso, um novo caminho será iniciado na intersecção de startX e startY.
  • ctx.fillStyle = color; definirá a cor usada para preencher o desenho
  • ctx.strokeStyle = color; define a cor selecionada como a cor do traço.

Em seguida, adicione um ouvinte de eventos à tela no mousemove evento. O mousemove evento invocará o Drawing() função.

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

Quando o usuário mover o mouse, crie uma função chamada desenho que primeiro verificará o isDrawing condição, se não for verdadeira, a função será encerrada.

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

Se o isDrawing condição estiver acontecendo, usaremos condições para verificar a ferramenta selecionada atual e atualizar adequadamente. Criaremos instruções case switch para cada uma das seguintes ferramentas:

  • à mão livre
  • círculo
  • triângulo
  • apagador
1
function drawing(e) {
2
  if (!isDrawing) return;
3

4

5
  switch (currentTool) {
6
    case "freehand":
7
      //  use  freehand tool

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
}

Para a ferramenta à mão livre, atualize a função conforme mostrado abaixo:

1
 function drawing(e) {
2
    if (!isDrawing) return;  
3
    switch (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
        // rest of the code

12
}}

Quando a ferramenta Mão livre for selecionada, faremos o seguinte:

  • ctx.moveTo(startX, startY); moverá o cursor de desenho para o ponto inicial
  • ctx.lineTo(e.offsetX, e.offsetY); adicionará uma linha do ponto inicial até a posição atual do mouse
  • ctx.stroke(); desenhará o caminho da linha com a cor selecionada.
  • startX = e.offsetX;  and startY = e.offsetY; redefinirá os pontos de partida.

Quando o retângulo for selecionado, atualize a função da seguinte maneira:

1
 function drawing(e) {
2
    if (!isDrawing) return;
3
    ctx.putImageData(initialImageData, 0, 0);
4
  
5
    switch (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 height = e.offsetY - startY;
17
        ctx.fillRect(startX, startY, width, height);
18
        ctx.beginPath();
19
        break;
20
}}
  • const width = e.offsetX - startX; A largura é obtida pela diferença entre a posição inicial, representada por startX e a coordenada x atual do ponteiro do mouse.
  • const height = e.offsetY - startY; Para obter a altura, obtemos a diferença entre a posição inicial, representada por startY e a coordenada y atual do ponteiro do mouse.
  • ctx.fillRect(startX, startY, width, height); o fillRect() método desenhará um retângulo preenchido. Este método recebe parâmetros na ordem fornecida.

Para desenhar um círculo, primeiro precisamos obter o raio do círculo, então usaremos o .arc() método para desenhar uma curva para o caminho especificado. O .arc() O método tem a seguinte sintaxe.

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

onde

  • x e y são as coordenadas x e y do centro do círculo
  • r é o raio do círculoqual é calculado pela distância do centro a qualquer ponto da circunferência do círculo. Para obter o raio do círculo, usaremos a equação de Pitágoras teorema
  • startAngle é o ângulo em que o caminho começa, medido em radianos. Em o contexto de um círculo, normalmente definido como 0, indicando o ponto inicial do caminho
  • endAngle é o ângulo em que o caminho termina em radianos. É obtido por 2*PI (corresponde a 360 graus)

Vamos obter o raio usando o Pitágoras teorema.

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

Agora, se substituirmos nossos valores no .arc() método, o código para desenhar um círculo ficará assim:

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

5
  switch (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 height = e.offsetY - startY;
17
      ctx.fillRect(startX, startY, width, height);
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
}

Por fim, para a ferramenta borracha, atualize a função da seguinte maneira:

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

5
  switch (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 height = e.offsetY - startY;
17
      ctx.fillRect(startX, startY, width, height);
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
}

A funcionalidade de apagar é semelhante à ferramenta à mão livre, exceto que ela usa a cor branca para cobrir quaisquer cores anteriores.

A última funcionalidade é a stopDrawing() função que acontece em mouseup evento que terá a seguinte aparência;

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

No mouseup evento, o desenho deve parar e o caminho atual deve ser fechado. Isso é para garantir que nenhuma outra operação de desenho ocorra até que uma nova mousedown evento ocorre.

O ctx.closePath() O método é usado para fechar o caminho atual, garantindo que a forma que está sendo desenhada seja finalizada.

Demonstração final

Vamos nos lembrar do que construímos! Aqui está a demonstração:

Conclusão

Este tutorial abordou como criar um aplicativo de desenho com Vanilla JavaScript. Você pode aprimorar ainda mais este aplicativo adicionando recursos como a capacidade de salvar desenhos, tamanhos de pincel personalizados, diferentes formas e ferramentas, e assim por diante.

Deixe uma resposta