In this article, we grasp the very basics of a canvas. We learn every piece of knowledge that is needed to create a simple drawing app that can also save our images and load them back to the editor.
The basics of a canvas
With the <canvas> element, we can draw images using JavaScript. The above gives us many possibilities, limited by what you can imagine and put into life with your code. When you put the <canvas> in your page, you start with a blank rectangle with its size defined by the width and height – those are the only canvas-specific attributes.
1 |
<canvas width="500" height="500"></canvas> |
As you can see, you can also put something inside of the <canvas> tag – if you do so, it acts as a fallback in case the browser does not support it. The chances of that happening are pretty slim though! The only culprit seems to be the Internet Explorer in version 8 and older.
Since we need to access the canvas from our code, let’s give it an id.
1 |
<canvas id="canvas" width="500" height="500"></canvas> |
1 2 3 |
window.onload = () => { const canvas = document.getElementById('canvas'); }; |
I’m using the window.onload here to make sure that our DOM tree is ready
Once we’ve got the canvas, we can access its context. It is a rendering context for our drawing surface, and we use it to draw any shape. Today we focus on 2D graphics, that’s we use the CanvasRenderingContext2D interface.
1 |
const context = canvas.getContext('2d'); |
Let’s start by drawing some simple rectangles. To do this, we use the fillRect function.
1 |
context.fillRect(0, 0, 50, 50); |
This gives us our first rectangle! The arguments of a function are:
- x-coordinate of the upper-left corner
- y-coordinate of the upper-left corner
- width of the rectangle
- height of the rectangle
As you can see, there is no argument for the color. The above is because we need to set it separately and to do that, we set the fillStyle attribute.
1 2 3 4 5 |
context.fillStyle = 'red'; context.fillRect(0, 0, 50, 50); context.fillStyle = 'blue'; context.fillRect(10, 10, 50, 50); |
The fillStyle property accepts different ways of specifying the color
There are many other different ways to draw shapes and define how they look, such as the strokeStyle.
Drawing on the canvas
To draw on the canvas, we need to attach some event listeners. Let’s start with mousedown.
When to start drawing
The
mousedown
event is fired at anElement
when a pointing device button is pressed while the pointer is inside the element.
1 2 3 4 5 6 7 8 9 10 11 12 |
let isDrawing = false; window.onload = () => { const canvas = document.getElementById('canvas'); const context = canvas.getContext('2d'); canvas.addEventListener('mousedown', startDrawing); }; function startDrawing() { isDrawing = true; } |
As soon as someone presses a mouse inside of our canvas, we want to start drawing!
When to stop drawing
Another event that we need is the mouseup.
The
mousedown
event is fired at anElement
when a pointing device button is pressed while the pointer is inside the element.
1 |
canvas.addEventListener('mouseup', stopDrawing); |
1 2 3 |
function startDrawing() { isDrawing = true; } |
The drawing itself
In our simple example, we draw a rectangle every time the mouse moves if the previous conditions are met: the user hovers over the canvas and he clicked a mouse once. To do that, we need the mousemove event.
The
mousemove
event is fired at an element when a pointing device (usually a mouse) is moved while the cursor’s hotspot is inside it.
1 2 3 |
const canvas = document.getElementById('canvas'); const context = canvas.getContext('2d'); canvas.addEventListener('mousemove', (event) => draw(event, context)); |
1 2 3 4 5 |
function draw(event, context) { if(isDrawing) { context.fillRect(event.pageX, event.pageY, 2, 2); } } |
As you can see, in this example, I use an arrow function because I want to pass both the event and the context to the draw function.
The event.pageX and event.pageY are coordinates of the cursor relative to the left edge of the entire document. This means that if the canvas is not in the top left corner of the page, it is not accurate. To account for it, we can use the getBoundingClientRect function.
1 2 3 4 5 6 7 8 |
function draw(event, canvas) { if(isDrawing) { const context = canvas.getContext('2d'); const rect = canvas.getBoundingClientRect(); context.fillRect(event.pageX - rect.left, event.pageY - rect.top, 2, 2); } } |
And thanks to that, we have a fully working functionality of drawing on a canvas!
Saving and loading images
To save and load images, we need a button and an input first.
1 2 3 4 5 6 7 8 9 |
<button id="save" type="button"> save </button> <input type="file" id="load" name="avatar" accept="image/png" > |
Saving an image
To save an image, we use the toDataUrl function. It returns the data in the URI format that contain the representation of the image in the type that we choose.
1 2 3 4 5 6 7 |
window.onload = () => { const canvas = document.getElementById('canvas'); const context = canvas.getContext('2d'); const saveButton = document.getElementById('save'); saveButton.addEventListener('click', () => save(canvas)); }; |
1 2 3 4 5 6 7 |
function save(canvas) { const data = canvas.toDataURL('image/png'); const anchor = document.createElement('a'); anchor.href = data; anchor.download = 'image.png'; anchor.click(); } |
In the save function, we create a dummy anchor, then attach the data, define a filename, and initiate the download.
Loading an image
To load an image, we listen for the change event of the input.
1 2 3 4 5 6 |
window.onload = () => { const canvas = document.getElementById('canvas'); const loadInput = document.getElementById('load'); loadInput.addEventListener('change', (event) => load(event, canvas)); }; |
Since the input stores the files in an array, we need to choose the last element:
1 2 3 |
function getFile(event) { return [...event.target.files].pop(); } |
When we have the file, we need to read it to interpret it accurately. To do it, we need the File Reader.
1 2 3 4 5 6 7 8 9 |
function readTheFile(file) { const reader = new FileReader(); return new Promise((resolve) => { reader.onload = (event) => { resolve(event.target.result); }; reader.readAsDataURL(file); }) } |
As soon as it reads the file successfully, we resolve the promise. The last thing to do is to load the image to the canvas.
1 2 3 4 5 6 7 8 9 |
function loadTheImage(image, canvas) { const img = new Image(); img.onload = function () { const context = canvas.getContext('2d'); context.clearRect(0, 0, canvas.width, canvas.height); context.drawImage(img, 0, 0); }; img.src = image; } |
Since the drawImage function that we want to use accepts an instance of the Image, we need to convert it first. When we have it, we clear the canvas and load the image.
Once we have all that, we can successfully load an image.
1 2 3 4 5 |
function load(event, canvas) { const file = getFile(event); readTheFile(file, canvas) .then((image) => loadTheImage(image, canvas)) } |
Putting it all together
My proposition of making it all work together is to use a class.
See the Pen
xoRJVx by Marcin Wanago (@mwanago)
on CodePen.
Summary
In this article, we’ve learned how to create a simple drawing using canvas. To do this we had to learn the very basics of the canvas and how to work with certain event listeners. While this is an elementary example, you are free to expand it and add new features.
but on tablet or mobile not working touch event please help me
Instead of “mousedown” use “touchstart” and “mousemove” use “touchmove” and “mouseup” use “touchend”.
https://www.w3schools.com/jsref/event_touchend.asp
https://www.w3schools.com/jsref/event_touchmove.asp
https://www.w3schools.com/jsref/event_touchstart.asp
Its interesting, but I’ve tryied to change stroke style to continous,red rounded pencil and has no success
To change to a continuous line and change color to red replace the draw(event) with the below snippet
draw(event) {
if (this.isDrawing) {
this.context.strokeStyle = “#000”;
this.context.lineJoin = “round”;
this.context.lineWidth = 1;
this.context.strokeStyle = “#FF0000”;
this.context.lineTo(
event.pageX – this.offsetLeft,
event.pageY – this.offsetTop
);
this.context.closePath();
this.context.stroke();
this.context.moveTo(
event.pageX – this.offsetLeft,
event.pageY – this.offsetTop
);
}
}
How can have a “regular” line instead of the dotted one?
watch this tutorial on how to make a simple drawing program.
https://www.youtube.com/watch?v=3GqUM4mEYKA
Amazing, thank you, you save my day