Open In App

Build A Drawing App using JavaScript

Last Updated : 27 Mar, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

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

ll

Build A Drawing App in HTML CSS & JavaScript

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.

HTML
<!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>
CSS
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;
}
Javascript
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:

po

Build A Drawing App in HTML CSS & JavaScript



Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads