Let's Make a Drawing Game with Node.js

Download

By now you have probably heard of node.js. It is an asynchronous web server built ontop of Google's V8 JavaScript engine (the same one that makes Chrome lighning fast). Using node, you can write scalable web services in JavaScript, that can handle a huge number of simultaneous connections, which makes it perfect as the backend of games, web chats and other real time tasks.

The Idea

Today we will be making a simple online drawing game. The app will let users draw on the page by dragging and moving their mice, and will display the results on a large canvas element. What sets is apart from all the other similar experiments though, is that people will see each other in real time as they do so. To achieve this, we will leverage the socket.io library for node.js, which uses a range of technologies from websockets to AJAX long polling to give us a real time data channel. Because of this, the example works in all modern browsers.

Installing node.js

To run the game you will need to install node.js. It shouldn't take more than a few minutes and is fairly straightforward. You can go ahead and download the installers from the official site. Or you can run this set of commands if you wish to install it from your terminal in Linux or OSX (you only need to run the first script: node-and-npm-in-30-seconds.sh).

After you finish installing, you will also get access to npm, the node package manager. With this utility you can install useful libraries and bits of code that you can import into your node.js scripts. For this example, we will need the socket.io library I mentioned above, and node-static, which will serve the HTML, CSS and JS files of the drawing application. Again, open up your terminal (or a new command prompt window if you are on Windows) and write the following command:

npm install [email protected] node-static

This shouldn't take more than a few minutes to complete.

Running the Application

If you want to just grab the files and test the app on your computer, you will need to download the archive from the button above, and extract it somewhere on your hard drive. After this, open a command prompt / terminal and navigate to the folder (of course you remember how the cd command works, don't you?). After this, type this command and hit return:

node app.js

You should be greeted with a socket.io debug message (otherwise probably your path is wrong; keep practicing with that cd command!). This means that everything is up and running! Now open http://localhost:8080 and you should see your very own copy of the demo. Nice!

These instructions also apply if you are following the steps of the article and are building the app from scratch. Which brings us back to the tutorial:

The HTML

The first step is to create a new HTML document. Inside it, we will put the canvas element which users will be drawing upon, and a div for holding the mouse pointers. Each mouse pointer will be a div with the .pointer css class that is absolutely positioned on the page (we won't be discussing the styling in this article, open assets/css/styles.css to take a look).

index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Node.js Multiplayer Drawing Game | Tutorialzine Demo</title>

        <!-- The stylesheets -->
        <link rel="stylesheet" href="assets/css/styles.css" />

        <!--[if lt IE 9]>
          <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
        <![endif]-->
    </head>

    <body>
        <div id="cursors">
            <!-- The mouse pointers will be created here -->
        </div>

        <canvas id="paper" width="1900" height="1000">
            Your browser needs to support canvas for this to work!
        </canvas>

        <hgroup id="instructions">
            <h1>Draw anywhere!</h1>
            <h2>You will see everyone else who's doing the same.</h2>
            <h3>Tip: if the stage gets dirty, simply reload the page</h3>
        </hgroup>

        <!-- JavaScript includes. Notice that socket.io.js is served by node.js -->
        <script src="/socket.io/socket.io.js"></script>
        <script src="http://code.jquery.com/jquery-1.8.0.min.js"></script>
        <script src="assets/js/script.js"></script>

    </body>
</html>

You can see that the canvas is set to a fixed width of 1900px and height of 1000px, but users with smaller displays will see only a part of it. A possible enhancement would be to enlarge or reduce the canvas in relation to the screen size, but I will leave that to you.

For the real-time communication channel between the users's browser and node.js to work, we need to include the socket.io library in both places, however you won't find the socket.io.js file included in the bottom of index.html in the download archive. This is because socket.io intercepts requests to /socket.io/socket.io.js and serves it itself so you don't have to explicitly upload this file with your application.

drawing-game.jpg
Draw Anywhere!

The Client Side

In other tutorials, we would usually name this section JavaScript, but this time we have JavaScript on both the client (the person's browser) and the server (node.js), so proper distinction must be made.

The code you see below runs in the person's browser. It uses socket.io to connect to the server and notifies us when an event occurs. That event is a message emitted by other clients and relayed back to us by node.js. The messages contain mouse coordinates, unique id for the user, and whether they are drawing or not in the moment.

assets/js/script.js

$(function(){

    // This demo depends on the canvas element
    if(!('getContext' in document.createElement('canvas'))){
        alert('Sorry, it looks like your browser does not support canvas!');
        return false;
    }

    // The URL of your web server (the port is set in app.js)
    var url = 'http://localhost:8080';

    var doc = $(document),
        win = $(window),
        canvas = $('#paper'),
        ctx = canvas[0].getContext('2d'),
        instructions = $('#instructions');

    // Generate an unique ID
    var id = Math.round($.now()*Math.random());

    // A flag for drawing activity
    var drawing = false;

    var clients = {};
    var cursors = {};

    var socket = io.connect(url);

    socket.on('moving', function (data) {

        if(! (data.id in clients)){
            // a new user has come online. create a cursor for them
            cursors[data.id] = $('<div class="cursor">').appendTo('#cursors');
        }

        // Move the mouse pointer
        cursors[data.id].css({
            'left' : data.x,
            'top' : data.y
        });

        // Is the user drawing?
        if(data.drawing && clients[data.id]){

            // Draw a line on the canvas. clients[data.id] holds
            // the previous position of this user's mouse pointer

            drawLine(clients[data.id].x, clients[data.id].y, data.x, data.y);
        }

        // Saving the current client state
        clients[data.id] = data;
        clients[data.id].updated = $.now();
    });

    var prev = {};

    canvas.on('mousedown',function(e){
        e.preventDefault();
        drawing = true;
        prev.x = e.pageX;
        prev.y = e.pageY;

        // Hide the instructions
        instructions.fadeOut();
    });

    doc.bind('mouseup mouseleave',function(){
        drawing = false;
    });

    var lastEmit = $.now();

    doc.on('mousemove',function(e){
        if($.now() - lastEmit > 30){
            socket.emit('mousemove',{
                'x': e.pageX,
                'y': e.pageY,
                'drawing': drawing,
                'id': id
            });
            lastEmit = $.now();
        }

        // Draw a line for the current user's movement, as it is
        // not received in the socket.on('moving') event above

        if(drawing){

            drawLine(prev.x, prev.y, e.pageX, e.pageY);

            prev.x = e.pageX;
            prev.y = e.pageY;
        }
    });

    // Remove inactive clients after 10 seconds of inactivity
    setInterval(function(){

        for(ident in clients){
            if($.now() - clients[ident].updated > 10000){

                // Last update was more than 10 seconds ago.
                // This user has probably closed the page

                cursors[ident].remove();
                delete clients[ident];
                delete cursors[ident];
            }
        }

    },10000);

    function drawLine(fromx, fromy, tox, toy){
        ctx.moveTo(fromx, fromy);
        ctx.lineTo(tox, toy);
        ctx.stroke();
    }

});

The basic idea is that we use socket.emit() to send a message to the node.js server on every mouse movement. This can generate a large number of packets, so we are rate-limiting it to one packet every 30 ms (the $.now() function is defined by jQuery and returns the number of milliseconds since the epoch).

The mousemove event is not called on every pixel of the movement, but we are using a trick to draw solid lines instead of separate dots - when drawing on the canvas, we are using the lineTo method, so that the distance between the mouse coordinates are joined with a straight line.

Now let's take a look at the server!

Server Side

After reading through the client side code you might be worried that the code on the server is even longer. But you will be mistaken. The code on the server side is much shorter and simpler. What it does is serve files when people access the url of the app in their browsers, and relay socket.io messages. Both of these tasks are aided by libraries so are as simple as possible.

app.js

// Including libraries

var app = require('http').createServer(handler),
    io = require('socket.io').listen(app),
    static = require('node-static'); // for serving files

// This will make all the files in the current folder
// accessible from the web
var fileServer = new static.Server('./');

// This is the port for our web server.
// you will need to go to http://localhost:8080 to see it
app.listen(8080);

// If the URL of the socket server is opened in a browser
function handler (request, response) {

    request.addListener('end', function () {
        fileServer.serve(request, response); // this will return the correct file
    });
}

// Delete this row if you want to see debug messages
io.set('log level', 1);

// Listen for incoming connections from clients
io.sockets.on('connection', function (socket) {

    // Start listening for mouse move events
    socket.on('mousemove', function (data) {

        // This line sends the event (broadcasts it)
        // to everyone except the originating client.
        socket.broadcast.emit('moving', data);
    });
});

With this our drawing app is complete!

Done!

Drawing is much more fun when you see other people doing it at the same time. Feel free to play with the example and improve it! Some ideas: it would be awesome to have different brushes, erasers, colors and shapes, or even country flags displayed next to the cursors. Go wild!

Bootstrap Studio

The revolutionary web design tool for creating responsive websites and apps.

Learn more

Related Articles

Cezar Luiz

Awesome!

Nice awesome thingy.

Quite interesting to see the impact of such a demo too :)
Let's see how long it takes for someone to fire up the console and fill the entire thing black in seconds.

Charlie Key

Hey Martin,

Awesome tutorial and little app. We liked it so much we went ahead and threw it up on our platform, Modulus.io, as a test. You can check it out at http://node-draw-257.onmodulus.net/. Seriously though, cool stuff.

Martin Angelov

Great platform! I will look into using it in the future :)

MontoGeek

Awesome Work, AWESOME :D Thanks for this "tutorial" to Node.js :D

War has just begun on the demo page ! ;)
Cool tuts, thanks !

Yonatan Ryabinski

Awesome stuff! As always great tutorial. Thank you for all your hard work!

Martin Angelov

Thank you for the awesome comments guys! Happy you like it :)

getting error like

Error: io is not defined
Source File: http://demo.tutorialzine.com/2012/08/nodejs-drawing-game/assets/js/script.js
Line: 27 ..
tested in ff 11.0 and chrome 21.0..

sorry...this is because of facebook block so ..js error in facebook prevent other js..

Wow ii lovee this tutorial good joob ;)

Caio Kawasaki

Can you create any buttons? Like to erase the draws, to save the screen and change brush size?

Martin Angelov

I have left this as an exercise for the reader :)

Caio Kawasaki

How to put it on my server?

Martin Angelov

You will need to have node.js on your server - this won't work on an ordinary web host. There are services that offer node.js support for free - appfog and heroku come to mind. You can try there. If you have your own server, you can install node by following the steps in the tutorial.

Elijah Good

lol..it's a war!!

Awesome!!!
Do you know any free hosting for Node.js?

Martin Angelov

Yep, see the comment to Caio above.

It`s funny!

Hi,can i translate this article (use chinese) and post on my blogger? and i will annotated references URL.

Martin Angelov

No, tutorials cannot be translated. Sorry.

Hi, thanks for the tutorial.

I have a quick question, on this line:

"ctx = canvas[0].getContext('2d'),"

Why do you use canvas[0]... instead of canvas.getContext("2d")? Is this for precautions only?

Martin Angelov

This is because the canvas variable actually holds a jQuery object, which doesn't have the getContext() method. By using canvas[0] I get the real canvas DOM object that does. You can alternatively do it like this: canvas.get(0)

Nice. It was fun to play around.

This is awesome. Well done.

I still don't understand how web sockets work though.

There aren't any other ajax requests (except to the web socket) and in the Chrome Network tab "Preview" of the web socket request I don't see any information being transmitted.

How does it work?

Martin Angelov

As always, wikipedia has all the answers.

wow!!!!
i can't stop drawing...help...heheheh
8)
amazing.

awesome as always :)

FINESTGOL

Hi, very nice app.

Your environment has been set up for using Node.js 0.8.8 (ia32) and NPM

C:\>J:

J:\>cd some

J:\some>node app.js

module.js:340
throw err;
^
Error: Cannot find module 'socket.io'
at Function.Module._resolveFilename (module.js:338:15)
at Function.Module._load (module.js:280:25)
at Module.require (module.js:362:17)
at require (module.js:378:17)
at Object.<anonymous> (J:\some\app.js:4:7)
at Module._compile (module.js:449:26)
at Object.Module._extensions..js (module.js:467:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:312:12)
at Module.runMain (module.js:492:10)

J:\some>

getting this flashed on my CMD,how to resolve ? I followed your steps but I am getting this !

Martin Angelov

You need to run following command from the article:

npm install socket.io node-static

This will install the libraries that you need.

What do you need to change to make this work on appfog?

Martin Angelov

I haven't tried running it there, but this should work: Change the server url on line 10 and port number to 80 of script.js and the port to 80 on line 13 of app.js.

Bernhard Hofmann

Really nice work, but there's always some brainless tit who'll come along and draw Swastikas or just scribble over other people's drawings. Would be nice to identify who and be able to vote they're kicked/blocked. But that's a bit much to ask I think. :)

Jim O'Mulloy

Beautiful, thanks. Might want to emphasise that you need to change the server url in script.js to the actual network host/ip address where app.js is running to make this work for browsing from different machines on network.

Hello friend,

How do I access my network all in my localhost, so we can share drawings?

already tried using my local ip:

ex.: 172.30.39.114:8080

They access however, does not see the cursor of them, nor the drawings: (.

Can you help me with this?

Sorry for the english, I used a translator.

I await response.

Martin Angelov

Change the server url on line 10 to your IP address, and port number to 8080 of script.js and the port to 8080 on line 13 of app.js.

Thanks man, it worked perfectly. I had tried this way, however I put 'http://', now I auditioned and worked without :).

Very grateful.

Hug.

Better yet, set url to null

var url = null;

It will figure out what url to use.

--Mark

bshankar

Nice work. There is an open source project to similar to this. Have a look at it.

https://chrome.google.com/webstore/detail/jgmchbbbbilnijcehedckeeofngdbigc

Syed Aoun Raza

also,
can you please explain it to me like im a 4 year old? at least with installing socket.io and node.js? what folder should i be on when running the commands
npm install socket.io node-static ?

also, im on a windows pc, so should i alter the port to 80 instead of 8080 ?

Martin Angelov

After you install node.js you will also get npm as well. Simply open up a command prompt and run the npm install command, it will know where to install them. Port 80 might be taken on your pc (by apache or skype, if you have any of those installed), so leave it to 8080.

Hello,

How could I insert a counter users online? Could get this value here? 'io.sockets.on (' connection ', function (socket)'

Thanks for your tutorial!

Martin Angelov

You can do this client-side. You can change the code of the interval that clears inactive clients (starting at line 98 of script.js) to something like this:

    setInterval(function(){

        var totalOnline = 0;
        for(ident in clients){
            if($.now() - clients[ident].updated > 10000){

                // Last update was more than 10 seconds ago.
                // This user has probably closed the page

                cursors[ident].remove();
                delete clients[ident];
                delete cursors[ident];
            }
            else totalOnline++;
        }

        $('#onlineCounter').html(totalOnline);
    },10000);

onlineCounter is the id of the div where you want the number displayed.

This would be more fun if so many people were not so rude!

Syed Aoun Raza

Thanks Martin Angelov, you pointed out the mistake rightly. :)
one more thing. the example is working super awesome on my local computer, checked on different browser set, but its not working over my network. on every network computer, the example is working but its not updating the network computer set. why is that?

Chau Thi

Wow,

very nice!
We really need more tools and maybe some private sessions for a serious app.
I can clearly see some high potential in this ;-)
Hehe

I'm gonna try out the server later on...

Pawel .P

Good work! Good game!
Best regards!

Hey Martin! Cool stuff :)
I downloaded the code, and changed port number to 4000 since i already have tomcat running on 8080. It does startup , but going to localhost:4000 on the browser doesn't update the drawing in two tabs simultaneously. What am I missing ?

Thanks :)

Martin Angelov

Look for messages in the JavaScript error console in your browser. This should give you some clues.

Hi! How do I add different colors?
And maybe some buttons, reset, changing brush etc?
Can you add this and update the game? Would be awesome!!!

please add each users canvas line color to random.. I would if I knew how.

Great tutorial,
A silly question.. You used Socket.io, could we also use SDP over websockets ?

Very nice, thanks, works well over local network (by changing URL to IP as described)

Question: my iPhone can see it over my local network, but cannot draw, any idea why?

maikeltunnissen

I have the same problem, i notice the nodejs server cant be reached when i am connecting to the host. This is some security issue i geuss?

Alain, the app is probably not listening to touchstart events.

Tester2009

Hello..
im already made it..
But,others cannot see it..
It mean,cannot play multiplayer..
Please,help me !
:/

Really neat!! Awesome work!
Put it at github mate.
I'm looking to play around it ;)

Ridvan Aliu

Great Tutorial !!!

Question:
When I run: node app.js in the terminal it gives me: info - socket.io started
and everything works as expected. However as soon as I close the terminal then it seems like the socket closes along with it and I cannot draw anymore.

So, in order for me to draw again then I will have open up the terminal and run node app.js

Any advise will be greatly appreciated.

Thanks,

Jose Manuel Perez

Hi!
i have been working on some of the suggestions at the comments.

random color, mobile version, users online, deployment at heroku, etc.

You can see the demo at: Drawing Game

And the source code at github: Drawing Game Source Code

Very cool.
It does not work on win 7 but works on xubuntu probably socket.io has problem on win7 platform.

I did similar project in combination with nodejs and http://www.image-mapper.com/ last week .

Thanks,

I still can't figure it out how to configure this website with appfog :( help? I upload app.js to appfog (update the app via ruby console) and when it want's to start the app again, it says socket.io not found :S

thanks for the code Jose Manuel Perez !!!

It works perfectly on my machine and also on my heroku space !!!

Great work! I want to adapt this to SVG-EDIT http://code.google.com/p/svg-edit/
It's a great project that could use a multi-user feature and a chat.
So if you don't mind I am going to ask you a ton of questions in the next few weeks.

I would really appreciate your help.

Thanks

my contribution to this web application is here

http://vrobbi-nodedrawing.herokuapp.com/

Karen Peng

Hi Martin,
I am writing a multi-player game using processing.js, node.js and socket.io. and come across a problem. Could you please give me some help?

My problem:
http://stackoverflow.com/questions/14729937/how-to-write-custom-emitevent-for-proessing-in-socket-io

Many thanks.

Hi :)

I am just a beginner in this area.. but i loved that.. It's so cool.
One question.

I've tried to use it on an android tablet with various browsers.. but it doesn't responded to touch actions..

What do you think.. is it possible to implement that ?
I am sure it is.. but how :)

Thanks,
Tom

Alejandro

Hi,

Love your work, thanks very much. Do you know any issues or additional ToDos to install this on win7 64 bits? It just hangs out and i'm doing the same as in a Win 7 PC and it works there.

Thanks,

Alejandro

I solved the problem, in app.js remove the addListener. Your handler should look like this:

function handler (request, response) {
    fileServer.serve(request, response);
}
Alejandro

Thanks very much Vic. BTW how Do you know how to Put my picture in the forum? I became a member and was expecting something like a button, login password, etc, like to put my picture, but don't see where at.

You have no idea how hard I tried to find a decent tutorial about the Node.js. Thank you so much for doing this!

Roman Sharf

I tried to get this to work in node version 0.10.4 and no luck. It turns out you need to tweak the code a tiny bit in app.js

// If the URL of the socket server is opened in a browser
function handler (request, response) {

    request.addListener('end', function () {
        fileServer.serve(request, response); // this will return the correct file
    });
    //You need this line for it to work
    request.resume();
}

Anyone know why that's the case? lol

Ow men! I have version: v0.10.10 . And i was editing the code checking ports and everything and i could figure out why its not working for me and then i saw your post :) Thx :) About your question: (my tip) is that some listener or events idk whatever can close end or pause request or socket. Something like that i have read in today's morning :)

Alejandro

Hi All,

I'd like to use this example to build a full spherical 360º x 180º canvas editor to draw spherical images with some tools like saving to png to polish in other software like gimp (I think it would be lot of time to put full set of tools for a imge editor). How can I do this? I want to draw like those mystical drawings in a world with a different sky and stars, and sea, but i can't find a software to do this spherically. How could I save this picture?

Many thanks,

Alejandro