How To Real-time Multi-touch

September 29, 2010 Learn how to use your fingers to transmit touch events to anyone in real-time. Follow this PubNub tutorial to learn how to use JavaScript Native Touch Events to capture your fingers' position and transmit coordinates in real-time.

Try it out!

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.

Step 1: Subscribe

First a real-time connection must be open to receive touch coordinates from other people. To open a connection, call the PUBNUB.subscribe() function. The following code is used to listen for touch events from other people:

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

Step 2: Touch Events

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:

mouseup, mousedown, mousemove, touchmove, touchstart, touchend and selectstart

PubNub comes with a device agnostic event binding function perfect for binding multiple events to the document. Here is an example of how this is done:

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

  1. Capture Touch Events.
  2. Apply Touch Offsets from center of page.
  3. Draw Touches Locally on Browser - update_player()
  4. Send Touch Coordinates - publish_updater()

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

Step 3: Publish

Next, send the captured coordinates. This is done by executing the PUBNUB.publish() function. Before this is done however, identification of the device sending the coordinates is important. Knowing which device the touch event comes from allows for tracking movement of each unique finger. Each device is tracked by a UUID generated from the PUBNUB.uuid() function.

// 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 PUBNUB.publish() function is called to frequently, network slamming will occur. When this happens, players will quickly saturate their bandwidth. It is best to transmit only a few coordinate pairs per second. Therefore a special purpose updater factory is necessary. The following function limits the rate of updates to prevent network slamming while always sending the latest information.

// 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 PUBNUB.publish() to transmit touch coordinates on the 'touch-receive' channel. Note that the message contains three variables.

  1. pointers - touch coordinates array.
  2. uuid - device UUID.
  3. last - last transmission counter.

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. PubNub's Cloud Service does not guarantee ordered message delivery. It is important to send message-counting-information when chronology is needed.

Step 4: Draw

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: PUBNUB.subscribe(). As you can see, a lot happens in the update_player() function. Its main purpose is to draw pointers on the screen. There is no upfront limit to the number of pointers/touches drawn. Notice the references to the sprite object. This object provides animation tweening between points transmitted from the PUBNUB.publish() function. This provides a smooth look with moving fingers and updated coordinates.

GitHub - Source Code - JavaScript

Visit the GitHub link to see all the code put in one piece.

Step 5: Bonus Tuning

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.

Prevent Event Bubbling

The PUBNUB.bind() function provides by default event bubbling prevention. This awards the opportunity to enable a gaming type mode in the web browser. This will prevent page scrolling and stop context menus from interrupting gameplay.

    e.cancelBubble = true;
    e.returnValue  = false;
    e.preventDefault();
    e.stopPropagation();

The PUBNUB.bind() function does cancels event bubbling by default. This also speeds up the browser in some cases.

Draw Local UI Changes First

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.

Always Out of Date

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

Sessions with UUID

Utilize the PUBNUB.uuid() function. This offers a way to identify each device and browser uniquely:

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

Time Synchronization

Utilize the PUBNUB.time() function. This provides a timestamp which can be used to synchronize players who are in different time zones or who have suspect local clock accuracy.

// Get Current Timestamp
PUBNUB.time(function(time) {
    console.log(time);
});

Never trust the player's local clock. Use the PUBNUB.time() function to synchronize players.

Animation Tweening

Make sure to use a keyframe framework which can animate between two points. PubNub provides a Sprite Keyframe Tweening Framework. It is remarkably lightweight and provides PNG animation support for cell based animation. There is no documentation for sprites, however this tutorial uses the sprite animation functions and it is easy to see how the functions can be used by example.

CSS Helpers

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.

Mobile Meta Tags

Include Mobile Meta Tags in the header section of the HTML file. This offers a resolution guideline for mobile browser to follow:

  1. Game Width/Height - <meta name=viewport content='width=320, height=480, user-scalable=0' />
  2. Load Bookmarked Page Without Mobile Safari - <meta name=apple-mobile-web-app-capable content=yes>
  3. Hide Status Header - <meta name=apple-mobile-web-app-status-bar-style content=black-translucent>
    <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>

PubNub Real-time

Take advantage of the PubNub Real-time Cloud Service. There are only two functions you need to know: PUBNUB.publish() and PUBNUB.subscribe(). Our JavaScript Push API focuses on speed and usability.

Get the PUBNUB JS Include