Estrutura HTML
A estrutura HTML consistirá nos seguintes componentes:
- UM
elemento que terá quatro opções suspensas, a saber; mão livre, círculo, retângulo e borracha
- 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 |
|
2 |
|
3 |
Drawing Tool
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
type="color" id="drawcolor" name="drawcolor" value="#00FFFF" class="form-control" />
|
21 |
|
22 |
|
23 |
|
24 |
|
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 oIsDrawing
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 destartY
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 destartX
estartY
. -
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 porstartX
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 porstartY
e a coordenada y atual do ponteiro do mouse. -
ctx.fillRect(startX, startY, width, height);
ofillRect()
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
ey
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 por2*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.