Build A Drawing App using JavaScript
A drawing app that lets us draw shapes like triangles, circles, and rectangles which can be made using HTML, CSS, and JavaScript. We’ll also have a brush tool to paint our drawings. With the shapes, we can create different designs, and with the brush, we can color them. We’ll include a filled checkbox so we can fill shapes with color. Additionally, there will be an eraser tool to remove drawings. We’ll add two buttons: one to clear the canvas with a single click and another to save the canvas as a JPG image. Let’s get started building our drawing app!
Preview Image
Approach:
- Create a folder containing three files: index.html, style.css, and script.js for our drawing app.
- Develop the index.html file which serve as the main entry point for our project.
- Utilize style.css to enhance the visual appearance and layout of our drawing app.
- Implement the necessary logic in script.js to enable functionality such as drawing shapes (rectangle, triangle, circle), selecting colors, saving images, clearing the canvas, and using the eraser tool.
Main Concept
- Canvas and Setup:
- The code initializes variables and selects HTML elements related to the canvas drawing app, such as tools, sliders, and colors.
- Sets up the canvas on page load by defining its dimensions and background color.
- Drawing Functions:
- Functions are defined for drawing various shapes like rectangles, circles, triangles, etc., using the 2D context of the canvas (ctx).
- Each function calculates shape parameters based on mouse positions and draws accordingly.
- Event Handling:
- Event listeners are attached to UI elements (buttons, sliders, color pickers, canvas) to respond to user interactions.
- Mouse events (mousedown, mousemove, mouseup) handle the initiation, tracking, and ending of drawing actions.
- Drawing Logic and State Management:
- The main drawing function (drawing) checks the selected tool and executes the corresponding drawing action.
- Handles freehand drawing with brush, pencil, or eraser, and ensures a smooth drawing experience by restoring the canvas state before each operation.
- Integrate HTML, CSS and JavaScript files to ensure seamless functionality and styling.
- Ensure that script.js contains all the required logic and functions for the drawing app to function properly.
Example :The below code example implements the HTML, CSS and JavaScript to create an Drawing App with interactive Image save operations.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,
initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="/style.css">
<!-- font awesome icons -->
<link rel="stylesheet" href=
"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css"
integrity=
"sha512-KfkfwYDsLkIlwQp6LFnl8zNdLGxu9YAA1QvwINks4PhcElQSvqcyVLLD9aMhXd13uQjoXtEKNosOWaZqXgel0g=="
crossorigin="anonymous" referrerpolicy="no-referrer" />
<script src="/script.js" defer></script>
<script src=
"https://kit.fontawesome.com/c353d71a36.js" crossorigin="anonymous">
</script>
</head>
<body>
<div class="container">
<section class="tools-board">
<div class="row">
<label for="" class="title"><strong>Tools</strong></label>
<ul class="options">
<li class="option tool" id="pencil">
<i class="fas fa-pencil" id="icon"></i>
<span>Pencil</span>
</li>
<li class="option active tool" id="brush">
<i class="fas fa-brush" id="icon"></i>
<span>Brush</span>
</li>
<li class="option tool" id="eraser">
<i class="fas fa-eraser" id="icon"></i>
<span>Eraser</span>
</li>
<li class="option">
<input type="range" id="size-slider" min="1"
max="30" value="5">
</li>
</ul>
</div>
<div class="row colors">
<label for="" class="title">Colors</label>
<ul class="options">
<li class="option"></li>
<li class="option selected"></li>
<li class="option"></li>
<li class="option"></li>
<li class="option">
<input type="color" value="#00FF00" name=""
id="color-picker">
</li>
</ul>
</div>
<div class="row">
<label for="" class="title"> <strong>Shapes</strong></label>
<ul class="options">
<li class="option tool" id="rectangle">
<i class="fa-solid fa-dice-one"></i>
<span>Rectangle</span>
</li>
<li class="option tool" id="circle">
<i class="fa-regular fa-circle"></i>
<span>Circle</span>
</li>
<li class="option tool" id="triangle">
<i class="fa-solid fa-mountain"></i>
<span>Triangle</span>
</li>
<li class="option tool" id="square">
<i class="far fa-square"></i>
<span>Square</span>
</li>
<li class="option tool" id="hexagon">
<i class="fa-solid fa-cube"></i>
<span>Hexagon</span>
</li>
<li class="option tool" id="pentagon">
<i class="fa-solid fa-dice-d6"></i>
<span>Pentagon</span>
</li>
<li class="option tool" id="line">
<i class="fa-solid fa-grip-lines"></i>
<span>Line</span>
</li>
<li class="option tool" id="arrow">
<i class="fa-solid fa-arrow-up"></i>
<span>Arrow</span>
</li>
<li class="option">
<input type="checkbox" id="fill-color">
<label for="fill-color"> Fill Color</label>
</li>
</ul>
</div>
<div class="row buttons">
<button class="clear-canvas">Clear Canvas</button>
<button class="save-img">Save As Image</button>
</div>
</section>
<section class="drawing-board">
<canvas></canvas>
</section>
</div>
</body>
</html>
body {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
background-color: #e3dede;
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif;
}
.container {
display: flex;
gap: 10px;
height: 540px;
max-width: 1050px;
width: 100%;
border-radius: 20px;
}
.tools-board {
width: 210px;
height: 590px;
border-radius: 20px;
background-color: #f8f7f6;
padding: 15px 22px 0;
}
.tools-board .row {
margin-bottom: 20px;
}
.row .options {
list-style: none;
margin: 10px 0 0 5px;
}
.row .options .option {
display: flex;
cursor: pointer;
align-items: center;
margin-bottom: 10px;
}
.option:is(:hover, .active) img {
filter: grayscale(20%);
}
.option :where(span, label) {
color: black;
cursor: pointer;
padding-left: 10px;
}
.option:is(:hover, .active) :where(span, label) {
color: #f4130b;
}
.option #fill-color {
height: 14px;
cursor: pointer;
width: 14px;
}
#fill-color:checked~label {
color: #e93e2b;
}
.option #size-slider {
width: 100%;
height: 5px;
margin-top: 10px;
}
.colors .options {
display: flex;
justify-content: space-between;
}
.colors .option {
height: 20px;
width: 20px;
border-radius: 30%;
margin-top: 3px;
position: relative;
}
.colors .option:nth-child(1) {
background-color: #fff;
border: 1px solid #bfbfbf;
}
.colors .option:nth-child(2) {
background-color: black;
}
.colors .option:nth-child(3) {
background-color: #ef4415;
}
.colors .option:nth-child(4) {
background-color: green;
}
.colors .option:nth-child(5) {
background-color: #c1f40a;
}
.colors .option.selected::before {
position: absolute;
content: "";
height: 12px;
top: 50%;
left: 50%;
width: 12px;
background: inherit;
border-radius: inherit;
border: 2px solid #fff;
transform: translate(-50%, -50%);
}
.colors .option:first-child.selected::before {
border-color: #ccc;
}
.option #color-picker {
opacity: 0;
cursor: pointer;
}
.buttons button {
width: 80%;
color: #fff;
border: none;
outline: none;
font-size: 0.9rem;
padding: 3px 0;
margin-bottom: 13px;
background: none;
border-radius: 5px;
cursor: pointer;
}
.buttons .clear-canvas {
color: black;
border: 1px solid #9fc39f;
transition: all 0.2s ease;
border: 2px solid black;
font-size: 13px;
}
.clear-canvas:hover {
color: #fff;
background: #6c757d;
}
.buttons .save-img {
background: white;
color: black;
border: 1px solid #6c757d;
}
.drawing-board {
flex: 1;
}
.drawing-board canvas {
width: 100%;
height: 590px;
border-radius: 10px;
}
const canvas =
document.querySelector("canvas"),
toolBtns =
document.querySelectorAll(".tool"),
fillColor =
document.querySelector("#fill-color"),
sizeSlider =
document.querySelector("#size-slider"),
colorBtns =
document.querySelectorAll(".colors .option"),
colorPicker =
document.querySelector("#color-picker"),
cleatCanvas =
document.querySelector(".clear-canvas"),
saveImage =
document.querySelector(".save-img"),
ctx = canvas.getContext("2d");
//global variabels wiht default values
let prevMouseX, prevMouseY, snapshot,
isDrawing = false,
selectedTool = "brush",
brushWidth = 5,
selectedColor = "#000";
const setCanvasBackground = () => {
ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = selectedColor;
}
window.addEventListener("load", () => {
// Setting canvas widht/height..
// offsetwidht/height returns
// viewbale widht/height of an element
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
setCanvasBackground();
});
const drawRect = (e) => {
// If fillColor isn't checked draw a react wiht
// border else draw rect wiht backgorund
if (!fillColor.checked) {
const width = prevMouseX - e.offsetX;
const height = prevMouseY - e.offsetY;
return ctx.strokeRect(e.offsetX, e.offsetY,
width, height);
}
const width = prevMouseX - e.offsetX;
const height = prevMouseY - e.offsetY;
ctx.fillRect(e.offsetX, e.offsetY, width, height);
}
const drawCircle = (e) => {
ctx.beginPath();
let radius = Math.sqrt(Math.pow((prevMouseX -
e.offsetX), 2)
+ Math.pow((prevMouseY - e.offsetY), 2));
ctx.arc(prevMouseX, prevMouseY, radius, 0, 2 * Math.PI);
fillColor.checked ? ctx.fill() : ctx.stroke();
}
const drawTriangle = (e) => {
ctx.beginPath();
ctx.moveTo(prevMouseX, prevMouseY);
ctx.lineTo(e.offsetX, e.offsetY);
ctx.lineTo(prevMouseX * 2 - e.offsetX, e.offsetY);
ctx.closePath();
fillColor.checked ? ctx.fill() : ctx.stroke();
}
// Function to draw a square
const drawSquare = (e) => {
const sideLength = Math.abs(prevMouseX - e.offsetX);
ctx.beginPath();
ctx.rect(e.offsetX, e.offsetY, sideLength, sideLength);
fillColor.checked ? ctx.fill() : ctx.stroke();
}
// Function to draw a hexagon
const drawHexagon = (e) => {
const sideLength =
Math.abs(prevMouseX - e.offsetX);
ctx.beginPath();
for (let i = 0; i < 6; i++) {
const angle = (2 * Math.PI / 6) * i;
const x = e.offsetX + sideLength
* Math.cos(angle);
const y = e.offsetY + sideLength
* Math.sin(angle);
ctx.lineTo(x, y);
}
ctx.closePath();
fillColor.checked ? ctx.fill() : ctx.stroke();
}
// Function to draw a pentagon
const drawPentagon = (e) => {
const sideLength =
Math.abs(prevMouseX - e.offsetX);
ctx.beginPath();
for (let i = 0; i < 5; i++) {
const angle = (2 * Math.PI / 5) *
i - Math.PI / 2;
const x = e.offsetX + sideLength
* Math.cos(angle);
const y = e.offsetY + sideLength
* Math.sin(angle);
ctx.lineTo(x, y);
}
ctx.closePath();
fillColor.checked ? ctx.fill() : ctx.stroke();
}
const drawLine = (e) => {
ctx.beginPath();
ctx.moveTo(prevMouseX, prevMouseY);
ctx.lineTo(e.offsetX, e.offsetY);
ctx.stroke();
}
const drawArrow = (e) => {
const headLength = 10;
const angle = Math.atan2(e.offsetY - prevMouseY,
e.offsetX - prevMouseX);
ctx.beginPath();
ctx.moveTo(prevMouseX, prevMouseY);
ctx.lineTo(e.offsetX, e.offsetY);
ctx.stroke();
// Draw arrowhead
ctx.beginPath();
ctx.moveTo(e.offsetX - headLength *
Math.cos(angle - Math.PI / 6),
e.offsetY - headLength *
Math.sin(angle - Math.PI / 6));
ctx.lineTo(e.offsetX, e.offsetY);
ctx.lineTo(e.offsetX - headLength *
Math.cos(angle + Math.PI / 6),
e.offsetY - headLength *
Math.sin(angle + Math.PI / 6));
ctx.closePath();
ctx.fill();
}
const startDraw = (e) => {
isDrawing = true;
prevMouseX = e.offsetX;
prevMouseY = e.offsetY;
ctx.beginPath();
ctx.lineWidth = brushWidth;
ctx.strokeStyle = selectedColor;
ctx.fillStyle = selectedColor;
snapshot = ctx.getImageData(0, 0, canvas.width,
canvas.height);
}
const drawPencil = (e) => {
ctx.lineTo(e.offsetX, e.offsetY);
ctx.stroke();
}
const drawing = (e) => {
if (!isDrawing) return;
ctx.putImageData(snapshot, 0, 0);
if (selectedTool === "brush" && selectedTool === "pencil"
|| selectedTool === "eraser") {
ctx.strokeStyle = selectedTool === "eraser"
? "#fff" : selectedColor;
ctx.lineTo(e.offsetX, e.offsetY);
ctx.stroke();
} else if (selectedTool === "rectangle") {
drawRect(e);
}
else if (selectedTool === "circle") {
drawCircle(e);
} else if (selectedTool === "triangle") {
drawTriangle(e);
} else if (selectedTool === "square") {
drawSquare(e);
} else if (selectedTool === "hexagon") {
drawHexagon(e);
} else if (selectedTool === "pentagon") {
drawPentagon(e);
} else if (selectedTool === "line") {
drawLine(e);
} else if (selectedTool === "arrow") {
drawArrow(e);
} else if (selectedTool === "curve") {
drawCurve(e);
}
else {
drawPencil(e);
}
}
toolBtns.forEach(btn => {
btn.addEventListener("click", () => {
document.querySelector(".options .active")
.classList.remove("active");
btn.classList.add("active");
selectedTool = btn.id;
console.log(selectedTool);
});
});
sizeSlider.addEventListener("change", () =>
brushWidth = sizeSlider.value)
colorBtns.forEach(btn => {
btn.addEventListener("click", () => {
document.querySelector(".options .selected")
.classList.remove("selected");
btn.classList.add("selected");
selectedColor = window.getComputedStyle(btn)
.getPropertyValue("background-color");
});
});
colorPicker.addEventListener("change", () => {
colorPicker.parentElement.style.background =
colorPicker.value;
colorPicker.parentElement.click();
});
cleatCanvas.addEventListener("click", () => {
ctx.clearRect(0, 0, canvas.width, canvas.height)
setCanvasBackground();
})
saveImage.addEventListener("click", () => {
const link = document.createElement("a");
link.download = `${Date.now()}`.jpg;
link.href = canvas.toDataURL();
link.click();
})
canvas.addEventListener("mousedown", startDraw);
canvas.addEventListener("mousemove", drawing);
canvas.addEventListener("mouseup", () => isDrawing = false);
Output:
Contact Us