Collaborative Drawing App: Drawing and Clearing from Other Tabs

Shawn
3 min readJun 3, 2022

--

In this section we see how to draw a shape on one tab and have it appear on others. We also see how to clear the drawing on all open tabs.

Verifying the Message Applies

Recall when the server receives a draw event, it simply broadcasts it to all clients. So the client needs to ensure 1) the message applies to the room it’s in, and 2) it wasn’t sent by this client. To do this, we refactor Socket.handleMessage to decode the JSON it received and do those checks:

async handleMessage(msg) {
const msgDecoded = JSON.parse(await msg.data.text());
if (msgDecoded.roomName === this.room.room && msgDecoded.roomId !== this.room.id) {
this.canvas.drawMessage(msgDecoded);
}
}

This code suggests we need references to the Room and Canvas objects. Because the Room object is defined before Socket in index.html, it can simply be passed as a parameter to the constructor.

const socket = new Socket(room);

However the Canvas object is defined after. You can either add the canvas as a property directly:

socket.canvas = canvas;

Or you could use a setter method if you want to be more formal:

setCanvas(canvas) {
this.canvas = canvas;
}

We’ll use the latter for this project. Now we define the method that actually draws what the message says:

drawMessage(msg) {
const {mode, color, startPoint, endPoint} = msg;
this.ctx.strokeStyle = color;
this.ctx.fillStyle = color;
if (mode === 'Line') {
this.drawLine(startPoint, endPoint);
} else if (mode === 'Hollow Rectangle') {
this.drawHollowRectangle(startPoint, endPoint);
} else if (mode === 'Filled Rectangle') {
this.drawFilledRectangle(startPoint, endPoint);
} else if (mode === 'Hollow Circle') {
this.drawHollowCircle(startPoint, endPoint);
} else if (mode === 'Filled Circle') {
this.drawFilledCircle(startPoint, endPoint);
}
this.ctx.strokeStyle = this.activeColor;
this.ctx.fillStyle = this.activeColor;
}

Note how this method is written to not interfere with the start/end points and color this user has. It has to change the stroke and fill styles of the current client’s canvas in order to use the right color sent from the server. But once it’s done drawing, it restores the color to what it was originally was.

Upon refreshing the tabs, you should see a shape drawn on one tab appear on the other!

Clearing on all Tabs

To blank out the drawing on all tabs, we add a method for the sockets to send the clear message, modify drawMessage to receive the message, and update the Clear button to actually send the socket message. Fortunately, these are all fairly small changes.

The method for sending the clear message, on the Socket class:

sendClear() {
this.socket.send(JSON.stringify({
type: 'clear',
roomId: this.room.id,
roomName: this.room.room
});
}

We then update handleMessage to tell when it’s a clear, and dispatch it to the right method on Canvas :

async handleMessage(msg) {
const msgDecoded = JSON.parse(await msg.data.text());
if (msgDecoded.roomName == this.room.room && msgDecoded.roomId !== this.room.id) {
if (msgDecoded.type === 'draw') {
this.canvas.drawMessage(msgDecoded);
} else if (msgDecoded.type === 'clear') {
this.canvas.clearMessage(msgDecoded);
}
}

}

On Canvas we need to update the existing method that clears the canvas to send a socket message, and add a method to actually do the clearing when the socket message is received:

clear() {
this.ctx.clearRect(0, 0, 500, 500);
this.socket.sendClear();
}
clearMessage(msg) {
this.ctx.clearRect(0, 0, 500, 500);
}

Putting it Together

Refresh the tabs and start drawing some shapes. Then, press Clear on one of the tabs, and watch the shapes disappear on both! The collaborative aspect of the project is now largely finished, but there’s one big missing feature. As soon as we close the tabs, the image is lost. Wouldn’t it be nice if we could save the image to the database, and view it again later? In the next section we’ll get started with that.

Here’s what the code should look like at the end of this section.

Collaborative Drawing App Series

--

--