DEV Community

Cover image for Create a drawing app using JavaScript and canvas
Adam Nagy
Adam Nagy

Posted on

Create a drawing app using JavaScript and canvas

In this tutorial we will create a simple drawing app in the browser. To do that we will use vanilla JS and the Canvas API.
After this tutorial you'll have a great overview of the canvas API and event handling in javascript.

Video tutorial

If you would watch a detailed step-by-step video instead you can check out the video I made covering this project on my Youtube Channel:

HTML markup

We'll start by wrapping the whole app into a section with the class of container. This will be used to align the toolbar and the drawing board.

Inside that we create a div which will hold our toolbar. I also set an id of toolbar for it so it will be easier to work with it in javascript.

Inside the toolbar we'll add a title for our app in an h1. Below that we'll add two input fields: one for color and one for the with of the line. For the color input I add the id of stroke as it will define the color of the stroke and for the number input I'll add the id of lineWidth. Don't forget to add the corresponding labels for these input fields. Lastly I'll add a button with the id of clear and this will be used to clear the drawing board.

The next thing we have to add in our html is the actual drawing board. It will be a canvas element, but for layouting purposes we'll wrap it into a div.

Lastly we need to add the script tag for our script at the bottom of the body.

<section class="container">
  <div id="toolbar">
        <h1>Draw.</h1>
        <label for="stroke">Stroke</label>
        <input id="stroke" name='stroke' type="color">
        <label for="lineWidth">Line Width</label>
        <input id="lineWidth" name='lineWidth' type="number" value="5">
        <button id="clear">Clear</button>
    </div>
    <div>
        <canvas id="drawing-board"></canvas>
    </div>
</section>
<script src="./index.js"></script>
Enter fullscreen mode Exit fullscreen mode

Add styles with CSS

I'll start by removing any browser defined paddings and margins. Also set the height of the body to 100% and remove the scrollbar with overflow: hidden.

body {
    margin: 0;
    padding: 0;
    height: 100%;
    overflow: hidden;
    color: white;
}
Enter fullscreen mode Exit fullscreen mode

For the title I'll add a gradient text color.

h1 {
    background: #7F7FD5;
    background: -webkit-linear-gradient(to right, #91EAE4, #86A8E7, #7F7FD5);
    background: linear-gradient(to right, #91EAE4, #86A8E7, #7F7FD5);
    background-clip: text;
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
}
Enter fullscreen mode Exit fullscreen mode

We'll also make the container 100% height, set the display to flex.

.container {
    height: 100%;
    display: flex;
}
Enter fullscreen mode Exit fullscreen mode

For the toolbar we will use flexbox with column direction. The width of the toolbar will have a fixed width of 70px. We'll add some spacing with 5px of padding and set a dark background for it.

For the toolbar elements I apply some basic styling. Feel free to copy these styles 👇

#toolbar {
    display: flex;
    flex-direction: column;
    padding: 5px;
    width: 70px;
    background-color: #202020;
}

#toolbar * {
    margin-bottom: 6px;
}

#toolbar label {
    font-size: 12px;
}

#toolbar input {
    width: 100%;
}

#toolbar button {
    background-color: #1565c0;
    border: none;
    border-radius: 4px;
    color:white;
    padding: 2px;
}
Enter fullscreen mode Exit fullscreen mode

Implementing the javascript part.

First we'll save references for the toolbar and the drawing board (canvas).

const canvas = document.getElementById('drawing-board');
const toolbar = document.getElementById('toolbar');
Enter fullscreen mode Exit fullscreen mode

Next we have to get the context of the canvas. We'll use this context to draw on the canvas. We can do that by calling the getContext method of the canvas. We'll draw in 2D so we have to provide that as a parameter.

const ctx = canvas.getContext('2d');
Enter fullscreen mode Exit fullscreen mode

In the next step we'll gather the offsets (distance between the canvas edges to the edge of the viewport) and save them. In this case the top offset will be 0px as the canvas takes the full height of the viewport and the left offset will be 70px as we have a fixed width sidebar on the left. Next we will calculate and set the height and the width of the canvas by subtracting the offsets from the viewport's width and height.

const canvasOffsetX = canvas.offsetLeft;
const canvasOffsetY = canvas.offsetTop;

canvas.width = window.innerWidth - canvasOffsetX;
canvas.height = window.innerHeight - canvasOffsetY;
Enter fullscreen mode Exit fullscreen mode

Now we will set some global variables. The isPainting variable will reflect whether we are currently drawing or not. We'll set a basic line width of 5 px. Lastly we'll declare two variables (startX & startY) which will hold the coordinates of the point which we started the drawing from.

let isPainting = false;
let lineWidth = 5;
let startX;
let startY;
Enter fullscreen mode Exit fullscreen mode

Let's start to add event listeners now. First we will add a click event listener to the toolbar. If the e.target.id is clear (so the clear button was clicked) then we'll call the clearRect function and provide it the width and height of the canvas. What this method will do is esentially set every pixel of the canvas to white inside the provided width and height values (so in this case the whole canvas).

toolbar.addEventListener('click', e => {
    if (e.target.id === 'clear') {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
    }
});
Enter fullscreen mode Exit fullscreen mode

Next we will handle the input changes for the line width and the drawing color. We'll use event delegation in this case. So instead of defining separate event handlers for each input field, we'll add only one event listener to the parent element and handle the events from there. We can differentiate which input field was changed by checking the value of e.target. If the color was changed we'll set the strokeStyle of the canvas context, if the lineWidth was changed then we update the value of the global lineWidth variable with the new value.

toolbar.addEventListener('change', e => {
    if(e.target.id === 'stroke') {
        ctx.strokeStyle = e.target.value;
    }

    if(e.target.id === 'lineWidth') {
        lineWidth = e.target.value;
    }
});
Enter fullscreen mode Exit fullscreen mode

Next we'll implement the drawing controls. When the mousedown event happens (the user clicks and holds the mouse button down) we'll set the isPainting variable to true and set the current mouse position's coordinates into startX and startY.

If the user releases the mouse button, then we'll set isPainting to false and call the stroke method of the context to colorize the already drawn path. We also have to call the beginPath method to close the path that the user drawn so far. We have to do this because if the user wants to draw another line it would start from this position, and this is not something that we want.

Lastly we'll add an event listener to the mousemove event. When the user moves the mouse we'll call the draw function which we'll implement next.

canvas.addEventListener('mousedown', (e) => {
    isPainting = true;
    startX = e.clientX;
    startY = e.clientY;
});

canvas.addEventListener('mouseup', e => {
    isPainting = false;
    ctx.stroke();
    ctx.beginPath();
});

canvas.addEventListener('mousemove', draw);
Enter fullscreen mode Exit fullscreen mode

In the draw function we'll first check the value of the isPainting variable if it is false we are not drawing so we'll just simply call return.

Next we'll set the line width to take the value from the global variable and set the lineCap to round. After this we'll draw a line by calling the lineTo method with the current mouse position's coordinates. One thing you have to be careful is to subtract the offset from the X coordinate because otherwise the drawn line would be offsetted with the width of the sidebar (70px). Lastly we only have to call the stroke method to give the line the color that we selected.

const draw = (e) => {
    if(!isPainting) {
        return;
    }

    ctx.lineWidth = lineWidth;
    ctx.lineCap = 'round';

    ctx.lineTo(e.clientX - canvasOffsetX, e.clientY);
    ctx.stroke();
}
Enter fullscreen mode Exit fullscreen mode

And this is it now you have a working drawing app!

If you stuck at any point you can watch the video or you can take a look at the source code on Codepen.

Where can you learn more from me?

I create education content covering web-development on several platforms, feel free to 👀 check them out.

I also create a newsletter where I share the week's or 2 week's educational content that I created. No bull💩 just educational content.

🔗 Links:

Latest comments (1)

Collapse
 
dflm25 profile image
Daniel Lucumi

Is it possible to make the canvas responsive and keep the proportions?