Building a Realtime Collaborative Microsoft Paint

malgamves profile image Daniel Madalitso Phiri ・6 min read

I built a thing, tweeted about it and promised to write about it. I didn't until now.

In this tutorial, we’ll be building a realtime collaborative graphics editor to mimic the functionality of the iconic Microsoft Paint on Windows XP. We take the editors basic functionality to manipulate pixels and extend that to multiple users enabling realtime collaboration. Additionally, we want to be able to track the number of users working together on a project at a particular time, as well as create a medium for them to communicate via chat.

At the end of this tutorial, you should have an in-browser graphics editor that looks like this.
Try a demo of it.

Collaborative Drawing

The aim of the project is to enable realtime collaboration, with possible use cases on distributed design teams or to classrooms. Powering our realtime collaboration is PubNub, it provides a secure, scalable and reliable realtime infrastructure to power any application through its global Data Stream Network. We'll use the PubNub JavaScript SDK to connect multiple clients using the graphics editor. The goal is to have changes made on clients screens to reflect on all the others.

Prerequisites for Building Collaborative Microsoft Paint

  • Basic JavaScript knowledge
  • Node and Npm
  • A browser like Chrome, Firefox or Safari

To start out, we'll need to create a few files and folders. We need to create the src folder and make two new files - main.css and app.js, we'll get back to these a little later. We'll need a few image assets to make the graphics editor look appealing. In the root directory, create a folder called images, download the image assets and paste them in the images folder. Now we’ll create an index.html file in the root directory and paste the following code:

In index.html, <header> holds our applications menu bar with a save button that enables us to save our work locally. Right after the <header> we have a <section> that houses the controls of our editor, we'll only use the brush option. <section> is followed by a <div> tag with an id of sketch that houses our <canvas> with an id of paint, the <canvas> tag is important because it lets us draw graphics in the browser. Take note of ids for both because we'll need to reference them when adding functionality to our graphics editor using JavaScript. Another thing we need to take note of is the <footer> tag which houses a couple of <div> tags which will enable us to change the colour of our brush and draw with multiple colours. At the end of the <footer>, we have an <h1> tag that we'll use to keep track of the number of collaborators online.

To add styling to our app, we need to paste the following into src > main.css

Post styling, we need to add functionality to our application. To do so, we'll paste the following into src > app.js and run through what that code does.

Our app.js file defines the functionality of our graphics editor. On the first line, we initialize PubNub and need to add our PubNub Publish and Subscribe keys. If you don't have a PubNub account, sign up for a free account to create your API keys. Sign up and log in using the form below:

After that, we define an object called mspaint that holds different properties. In the start property, we use a function that initializes our canvas by doing a few things:

  • We set the context to '2d' as we will be only drawing in 2d
  • We set the height and width of our canvas

Here, we use the subscribe() method to subscribe to a channel, draw which we defined earlier. We then define a callback function drawFromStream() which collects drawings done by other users and makes those drawings reflect on a users canvas. Next, we initialize PubNub Presence to keep track of the number of users using the graphics editor at one time.

We also have a few event listeners that track when the mouse button goes up, moves and goes back down. The event listeners translate these movements to drawings on the canvas. When a user is done drawing, we want a user’s activity on their canvas to appear on the canvases of every other user they are collaborating with. We know that a user is done drawing when their mouse button goes back up. So we take advantage of the event listener that tracks when a users mouse button goes up. It is here where we add the publish() method from the PubNub JavaScript SDK so that a user’s drawing reflects on every other user’s canvas too.

To run our graphics editor, we need to serve the files locally. To do this, we'll have to install http-server by running npm -g i http-server. In our projects root directory, run http-server in your terminal and we have a working editor.

Collaborative Drawing

We've got the realtime collaborative portion done, now we want to enable communication between collaborators via chat. The PubNub Chat is a scalable and reliable In-App Messaging API that powers chat-based experiences.

We'll implement this by adding another item to our menu bar that opens up a modal containing the chat section. In the index.html file, add <li><a href="#" id="openModal">Chat</a></li> to the <header> right after the last <li> tag. Now that we have the additional item in the menu bar, we need to paste this code that makes up our modal right after the closing </header> tag.

<!-- The Modal -->
      <div id="myModal" class="modal">
        <!-- Modal content -->
        <div class="modal-content">
          <span class="close">&times;</span>
          <p>Type your message and Press Enter.</p>
          <input id="input" placeholder="Your Message Here" />
          <div id="box"></div>

The modal contains an input field where users can type messages they want to send to each other. The messages sent and received are displayed in the <div> tag with an ID of box, we take note of the ID because we use it to update the <div> with new messages in the chat. In app.js, the code below adds functionality to the modal.

let modal = document.getElementById("myModal");
let btn = document.getElementById("openModal");
let span = document.getElementsByClassName("close")[0];
btn.onclick = function() {
  modal.style.display = "block";

span.onclick = function() {
  modal.style.display = "none";

window.onclick = function(event) {
  if (event.target == modal) {
    modal.style.display = "none";

What this code does is give us different ways to interact with and use the modal. Now that we have it working, we need to work on connecting different user’s chats to each other using PubNub. At the bottom of app.js, we have a function chat() that subscribes to chat message data in our collaborative graphics editor as well as a callback function - publishMessages() which publishes message data to the chat channel making all messages sent accessible for all the users of the chat in the graphics editor. We then initialize variables - box for the <div> tag that holds chat responses, input that handles data from the <input> tag and a channel where the communication occurs in PubNub’s Data Stream. We then subscribe to the channel using the subscribe() method and add a listener that updates the users with the latest messages from the chat by pushing them into our chat modal. We also have an eventListener() method to publish messages to the channel on a keypress. We then use the onload() method to make sure the chat functionality is enabled when the window loads. Run http-server and have a working chat to add to our collaborative graphics editor.

PubNub Chat Demo

Now we have a Realtime Collaborative Microsoft Paint site that can track the number of online users and connect them through a chat. If you have any questions, feel free to reach out to me on Twitter.

Sorry I took so long to release it. I hope you found it useful it.

Posted on by:

malgamves profile

Daniel Madalitso Phiri


Tech x Culture - Tech Writer - Disk Jockey - Host #RushingFwd - @devcon_zm, @CodeCastZM - Prev. @HasuraHQ - Int'l Speaker - Zambian 🇿🇲 #DevRel


markdown guide