Saturday, November 6, 2010

Real-Time Collaborative Painting With Node.JS and Socket.IO

Introduction

I <3 Node.js!. This post will show you how to put together an amazing collaborative painting web app using Node.jS and web sockets in about 130 lines of code. The whole source code can be found here. I'll mention it's modeled loosely after the chat example found in the Socket.io-node source code.

This is more of a simple demo at the moment, but it proves a few points.
1) Node is seriously awesome (and easy to get up and running)
2) Socket.io is equally awesome
3) It's easy to support the iPhone and other touch devices
4) Canvas is cool too :)

Unfortunately I do not currently have a live demo. I'm still hitting https://api.no.de/coupons hoping for a free trial to one of Joyent's Smart Machine's, but so far no luck :)

A Picture

*The red is what you drew, the blue is what someone else drew.

Overview

There are pretty much only 3 files of consequence and you can view them all here:

1) collabpaint.html

HTML file that just gives you a canvas and contains some simple CSS content. Used to display the page.

2) collabpaint.js

This file is responsible for drawing to your canvas. It contains all of the event listeners and code for listening for messages from the node server.

3) server.js

Tells node to start an http server and socket.io instance to handle messaging between clients.


Getting started with Node


There are a handful of great tutorials for getting started with Node.

In general if you just want to get started with node, it's very easy. There are several tutorials linked from the node wiki page here:
https://github.com/ry/node/wiki

I think I read all of them for doing this example. The biggest things for me was basically figuring out how to install modules. Other than, I noticed that node crashes pretty easily if you're doing something wrong and it doesn't always give you a useful error messages.

Another big thing. Stick with the current stable version of node! The docs mention 0.3.0 on the website, but the current stable version is actually 2.4. That means, if you're checking out the source code with git, you'll need to switch away from the master branch to the 0.2.x branch like so, before configuring and installing Node:
git clone https://github.com/ry/node.git
cd node
git checkout -b v0.2 origin/v0.2 
./configure
make
sudo make install

Also, the docs for version 0.2.4 can be found here.

Adding support for web sockets with Socket.IO


Coming soon...

Canvas Quirks


The biggest problem I ran into with the canvas stuff was figuring out where on the screen to draw. I noticed that the coordinates I retrieved initially were only working on Google Chrome (e.offsetX and e.offsetY). Later I changed to a way that seemed not to work if the page had been scrolled down at all. My current solution of relying on the clientX and clientY, subtracting the target offsetX and offsetY and then adding the window scrollX and scrollY seems to work okay Chrome, Safari and Firefox. See the code:

coords = {
    x:e.clientX - e.target.offsetLeft + window.scrollX,
    y:e.clientY - e.target.offsetTop + window.scrollY
};

Adding iOS Support


There were two things I needed to change to allow this to work really well on an iPhone (and presumably other mobile devices). First, I used a media query to take away all of the extraneous content on the page. The mobile version is just a big canvas. No silly border and no header.

@media screen and (max-width: 320px) {
  h1 {
   display: none;
  }
  canvas {
   border: none;
   border-radius: none;
   -webkit-box-shadow: none;
  }
 }

Next I had to listen for special events in order to tell when we were drawing on the iPhone:
// iOS alternative to mouse move
canvas.ontouchmove = function(e) {
 move(e, true);
};

Lastly, we needed to handle multi-touch. This was the trickiest thing I guess, but a few Google searches found me some great docs from Apple website about how to capture those events.

for (var i=0; i<e.targetTouches.length; i++) {
    coords = {
        x: e.targetTouches[i].clientX,
        y: e.targetTouches[i].clientY
    };   
    drawCircle("red", coords);
    send(coords);
}

This may not be the fanciest way to detect touch events. I'm not doing any real feature detection, but the browsers don't seem to mind too much about having these extra event handlers in there, so I'm pretty happy with the solution.


The Future

Well, I'm working on a lot of variations to this simple app.

1) Different "sessions" where people can only draw with people they invite
2) Different colors / Brush types
3) An eraser
4) Doing fun things with sound.

Some of these are already available in the code itself under different branches.

3 comments:

  1. Thanks for posting this, this is awesome!

    ReplyDelete
  2. Hi I am an engineering student.I m trying to create a real time collaborative online editor for my college project. I have a good knowledge of PHP AJAX and Javascripts/JQuery however.I m very new to socket programming and NodeJS.This is an excellent tutorial to get me started.I also want to include feature Different "sessions" where people can only draw with people they invite. Would you please please kindly help me !!!

    ReplyDelete
  3. @Rohit - There's an example on multiroom chat in the Socket.io examples. Guess that might guide you in your effort.

    ReplyDelete