Real-time Multi-Touch iPad Room
*** Performance depends on network speed.
GitHub - Source Code - JavaScript
This tutorial is composed of several steps which describe the process of transmitting real-time touch events.
First a real-time connection must be open to receive
touch coordinates from other people.
To open a connection, call the
// Listen for Touch Coordinates PUBNUB.subscribe({ channel : 'touch-receive', callback : update_player });
The code above opens a connection with the channel 'touch-receive' which listens for touch events from other people. This means when another person moves their finger or mouse around the screen, their coordinates will be transmitted to everyone's screen. This of course needs to be programmed. This guide will walk you through how to program real-time coordinates transmission.
Next, capture each finger's position on the screen. To do this, a browser's event object is needed. The event object is available when binding events to the document. The following events are of interest in this tutorial:
// Set Browser to Gaming Mode PUBNUB.bind( 'mouseup,mousedown,mousemove,touchmove,' + 'touchstart,touchend,selectstart', document, update_pointer );
Each event is bound to a master touch tracking function called update_pointer(). This function's purpose will grab the first event fired by the browser containing touch coordinates and prevent remaining events from bubbling. The event object contains touch coordinates. Here is the update_pointer() function which captures touch coordinates:
// Capture Touch Coordinates function update_pointer(e) { var pointers = current_player.pointers = mouse(e) , offset_top = offset( painter, 'Top' ) , offset_left = offset( painter, 'Left' ); // Apply Touch Offset each( pointers, function(pointer) { pointer[0] -= offset_left + 20; pointer[1] -= offset_top + 60; } ); // Draw Touches Locally update_player({ pointers : current_player.pointers, uuid : '', now : now() }); // Send Coordinate Information publish_updater(); }
The update_pointer() function these does four things:
The function starts by capturing all touch X/Y Coordinates with a function named mouse(). This is a cross device function which detects one mouse cursor or multiple finger touches. It is a simple function which returns an array of [ x, y ] coordinate pairs:
function mouse(e) { // Bubble if No Touch Coordinates if (!e) return [[0,0]]; // Detect Touch Coordinates Available var tch = e.touches && e.touches[0] , mpos = []; // Touch Coordinates if (tch) { PUBNUB.each( e.touches, function(touch) { mpos.push([ touch.pageX, touch.pageY ]); } ); } // Mouse Coordinates else if (e.pageX) { mpos.push([ e.pageX, e.pageY ]); } // Mouse Coordinates IE else { try { mpos.push([ e.clientX + body.scrollLeft + doc.scrollLeft, e.clientY + body.scrollTop + doc.scrollTop ]); } catch(e){} } // Return Captured Coordinates return mpos; }
The mouse() function captures both standard mouse coordinates and finger touch coordinates. If using an iPhone/iPad/Android device, the mouse function will return an array of arrays which looks like this (with three (3) fingers):
[ [x,y], [x,y], [x,y] ]
Next, send the captured coordinates.
This is done by executing the
// Create UUID PUBNUB.uuid(function(uuid) { console.log(uuid); });
Beware, there is another pitfall!
Those readers who have bound mouse movement events before know
that between 100 and 900 events fire each second.
If the
// Provide Rate Limited Function Call function updater( fun, rate ) { var timeout, last = 0; // Return Rate Limited Function return function() { var right_now = now(); // Time to Execute Function? if (last + rate > right_now) { clearTimeout(timeout); timeout = setTimeout( runnit, rate ); } else { last = now(); fun(); } } }
The updater() function provides a simple interface for creating a rate limited function. It's a function which returns a function. When a rate limited function is called, even if it is called 100,000 times in a second, the inner function will only be called as often as specified. Here is how to use the updater() function:
// Called on Touch Move Events var publish_updater = updater( function() { // Must be Ready Before Sending. if (!current_player.ready) return; // Send Player Touch Coordinates. PUBNUB.publish({ channel : 'touch-receive', message : { pointers : current_player.pointers, uuid : current_player.info.uuid, last : last++ } }); }, wait );
The inner function uses
This data is transmitted to anyone listening
on the 'touch-receive' channel.
With this information, all fingers and mice can be drawn
on everyone's screen at the same time with live positioning.
The last transmission counter
is used to identify which message is the most recent.
The final step is to draw the received coordinates. This is done by adding an absolutely positioned div to the document. The div will of course have a nifty background image. In the case of this tutorial, the background image will be a fingerprint. The draw function is called update_player(). Here is the source of update_player():
// Called for All Touch Events (from everyone) function update_player(message) { var uuid = message['uuid'] || 'self' , last = message['last'] , pointers = message['pointers'] , i = 0; // Leave if transmitted coords from yourself. if (!current_player['info']) return if (uuid == current_player.info.uuid) return; // Is this a New Touch? if (!points[uuid]) { points[uuid] = { last : last, touches : [] }; } // For Each Pointer each( pointers, function(point) { // Create Sprite DIV for the Pointer if (!points[uuid].touches[i]) { points[uuid].touches[i] = { xy : point, sprite : sprite.create({ image : { url : 'print.png', width : 40, height : 60, offset : { top : 0, left : 0 } }, cell : { count : 1 }, left : point[0], top : point[1], framerate : 60 }) }; // Set Random Fingerprint Opacity var opacity = Math.random(); css( points[uuid].touches[i].sprite.node, { opacity : opacity < 0.5 ? 0.5 : opacity } ); } // Draw Self Touches if (uuid == 'self') { css( points[uuid].touches[i].sprite.node, { left : point[0], top : point[1] } ); } // Draw Remote Touches (From other People) else { sprite.move( points[uuid].touches[i].sprite, { left : point[0], top : point[1] }, wait ); } // Next Pointer i++; } ); }
This function gets called when a message is received
in Step 1:
GitHub - Source Code - JavaScript
Visit the GitHub link to see all the code put in one piece.
Several highly useful tactics will provide a smooth user experience. The following tips will provide helpful as guidelines in all real-time applications built on any platform or service.
The
e.cancelBubble = true;
e.returnValue = false;
e.preventDefault();
e.stopPropagation();
The
A great tactic to provide fast UI and improved user experience is to draw all local changes before sending updates remotely. This will dramatically improve perceived performance. Drawing local User changes will provide smooth and non-jumpy graphics.
This is important! Always assume everyone is out of date. With real-time applications, this is critical. Especially since network connections drop unexpectedly for many reasons. Also a person may refresh the page (F5) or leave by accident. Therefore, always send and receive data whenever something has changed. Keeping the mindset of "everyone is always out of date" allows more stable application development practices. Finally provide an interface which allows piers to asking each other the question: "Am I up to date?".
Utilize the
// Create UUID PUBNUB.uuid(function(uuid) { console.log(uuid); });
The UUID can be used as a one-time session token. This can be passed in the publish() message to identify the origin of messages from unique sessions. Optionally store the UUID in a cookie or localStorage to provide longer lasting sessions.
Utilize the
// Get Current Timestamp PUBNUB.time(function(time) { console.log(time); });
Never trust the player's local clock.
Use the
Make sure to use a keyframe framework which can animate between
two points.
The following is a collection of CSS attributes which improve user experience for games in the browser especially in webkit and mobile phones. The following CSS is in use for this tutorial:
body {
margin: 0;
padding: 0;
height: 100%;
overflow: hidden;
overflow-y: hidden;
-webkit-user-select: none;
-webkit-touch-callout: none;
-webkit-tap-highlight-color: rgba(0,0,0,0);
-webkit-text-size-adjust: none;
}
These CSS attributes assigned to the body selector prevent page scroll bars. The webkit CSS attributes prevent text from being selected in Android iPhone, iPad, iPod, Safari and Chrome.
Include Mobile Meta Tags in the header section of the HTML file. This offers a resolution guideline for mobile browser to follow:
<meta
name=viewport
content='width=320,height=480,user-scalable=0'>
<meta
name=apple-mobile-web-app-capable
content=yes>
<meta
name=apple-mobile-web-app-status-bar-style
content=black-translucent>
Take advantage of the