Let’s Make a Drawing Game with Node.js

Let’s Make a Drawing Game with Node.js

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. If you are on Windows, you can go ahead and download the installer from its official site. If you are on Linux or OSX, you will need to run this set of commands in your terminal (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, or 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 socket.io 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.

Draw Anywhere!

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!

Join our newsletter and get our PSDs!17,462 people learn about HTML5, JS and more. Join them!

by Martin Angelov

Martin is a web developer with an eye for design from Bulgaria. He founded Tutorialzine in 2009 and publishes new tutorials weekly.

74 Comments

  1. jerev says:

    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.

  2. Charlie Key says:

    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.

    1. Martin Angelov says:

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

  3. MontoGeek says:

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

  4. Tailzip says:

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

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

  6. Martin Angelov says:

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

  7. sandeep says:

    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..

    1. sandeep says:

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

  8. samir says:

    Wow ii lovee this tutorial good joob ;)

  9. Caio Kawasaki says:

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

    1. Martin Angelov says:

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

  10. Caio Kawasaki says:

    How to put it on my server?

    1. Martin Angelov says:

      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.

  11. Manu says:

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

    1. Martin Angelov says:

      Yep, see the comment to Caio above.

  12. Looly says:

    It`s funny!

  13. win says:

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

    1. Martin Angelov says:

      No, tutorials cannot be translated. Sorry.

  14. sim says:

    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?

    1. Martin Angelov says:

      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)

  15. Kumaran says:

    Nice. It was fun to play around.

  16. Nathan says:

    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?

    1. Martin Angelov says:

      As always, wikipedia has all the answers.

  17. Cesar says:

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

  18. Saquib says:

    awesome as always :)

  19. FINESTGOL says:

    Hi, very nice app.

  20. ali says:

    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 !

    1. Martin Angelov says:

      You need to run following command from the article:

      npm install socket.io node-static

      This will install the libraries that you need.

  21. Nico says:

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

    1. Martin Angelov says:

      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.

  22. Bernhard Hofmann says:

    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. :)

  23. Jim O'Mulloy says:

    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.

  24. Igor says:

    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.

    1. Martin Angelov says:

      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.

      1. Igor says:

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

        Very grateful.

        Hug.

      2. Mark says:

        Better yet, set url to null

        var url = null;

        It will figure out what url to use.

        --Mark

  25. bshankar says:

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

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

  26. Syed Aoun Raza says:

    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 ?

    1. Martin Angelov says:

      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.

  27. July says:

    Hello,

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

    Thanks for your tutorial!

    1. Martin Angelov says:

      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.

  28. J says:

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

  29. Syed Aoun Raza says:

    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?

  30. Chau Thi says:

    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...

  31. Pawel .P says:

    Good work! Good game!
    Best regards!

  32. Shreya says:

    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 :)

    1. Martin Angelov says:

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

  33. Kasper says:

    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!!!

  34. junkman says:

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

  35. Jube says:

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

  36. Alain says:

    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?

    1. maikeltunnissen says:

      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?

  37. Travis says:

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

  38. Tester2009 says:

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

  39. Erick says:

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

  40. Ridvan Aliu says:

    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,

  41. 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

  42. kleo says:

    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,

  43. Mirko says:

    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

  44. vrobbi says:

    thanks for the code Jose Manuel Perez !!!

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

  45. DEZ says:

    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

  46. vrobbi says:

    my contribution to this web application is here

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

  47. Karen Peng says:

    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.

  48. Tom says:

    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

  49. Alejandro says:

    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

    1. Vic says:

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

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

        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.

  50. Roy Li says:

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

  51. Roman Sharf says:

    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

    1. Trki says:

      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 :)

  52. Alejandro says:

    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

Add Comment

Add a Reply

HTML is escaped automatically. Surround code blocks with <pre></pre> for readability.
Perks:   **bold**   __italics__   [some text](http://example.com) for links