Multi-touch interaction
Pointer events extend DOM input events to support various pointing input devices such as pen/stylus and touch screens as well as mouse. The pointer is a hardware-agnostic device that can target a specific set of screen coordinates. Having a single event model for pointers can simplify creating websites, applications and provide a good user experience regardless of the user's hardware.
Pointer events have many similarities to mouse events but they support multiple simultaneous pointers such as multiple fingers on a touch screen. This additional feature can be used to provide richer user interaction models but at the cost of additional complexity in the multi-touch interaction handling. This document demonstrates via example code, using pointer events with different multi-touch interactions.
A live version of this application is available on GitHub. The source code is available on GitHub; pull requests and bug reports are welcome.
Example
This example demonstrates using pointer events' various event types (pointerdown
, pointermove
, pointerup
pointercancel
, etc.) for different multi-touch interactions.
Define touch targets
The application uses <div>
to define three different touch target areas.
html
<style>
div {
margin: 0em;
padding: 2em;
}
#target1 {
background: white;
border: 1px solid black;
}
#target2 {
background: white;
border: 1px solid black;
}
#target3 {
background: white;
border: 1px solid black;
}
</style>
Global state
To support multi-touch interaction, preserving a pointer's event state during various event phases is required. This application uses three arrays to cache event state, one cache per target element.
js
// Log events flag
const logEvents = false;
// Event caches, one per touch target
const evCache1 = [];
const evCache2 = [];
const evCache3 = [];
Register event handlers
Event handlers are registered for the following pointer events: pointerdown
, pointermove
and pointerup
. The handler for pointerup
is used for the pointercancel
, pointerout
and pointerleave
events, since these four events have the same semantics in this application.
js
function setHandlers(name) {
// Install event handlers for the given element
const el = document.getElementById(name);
el.onpointerdown = pointerdownHandler;
el.onpointermove = pointermoveHandler;
// Use same handler for pointer{up,cancel,out,leave} events since
// the semantics for these events - in this app - are the same.
el.onpointerup = pointerupHandler;
el.onpointercancel = pointerupHandler;
el.onpointerout = pointerupHandler;
el.onpointerleave = pointerupHandler;
}
function init() {
setHandlers("target1");
setHandlers("target2");
setHandlers("target3");
}
Pointer down
The pointerdown
event is fired when a pointer (mouse, pen/stylus or touch point on a touchscreen) makes contact with the contact surface. The event's state must be cached, in case this down event is part of a multi-touch interaction.
In this application, when a pointer is placed down on an element, the background color of the element changes, depending on the number of active touch points the element has. See the update_background
function for more details about the color changes.
js
function pointerdownHandler(ev) {
// The pointerdown event signals the start of a touch interaction.
// Save this event for later processing (this could be part of a
// multi-touch interaction) and update the background color
pushEvent(ev);
if (logEvents) {
log(`pointerDown: name = ${ev.target.id}`, ev);
}
updateBackground(ev);
}
Pointer move
The pointermove
handler is called when the pointer moves. It may be called multiple times (for example, if the user moves the pointer) before a different event type is fired.
In this application, a pointer move is represented by the target's border being set to dashed
to provide a clear visual indication that the element has received this event.
js
function pointermoveHandler(ev) {
// Note: if the user makes more than one "simultaneous" touch, most browsers
// fire at least one pointermove event and some will fire several pointermoves.
//
// This function sets the target element's border to "dashed" to visually
// indicate the target received a move event.
if (logEvents) {
log("pointerMove", ev);
}
updateBackground(ev);
ev.target.style.border = "dashed";
}
Pointer up
The pointerup
event is fired when a pointer is raised from the contact surface. When this occurs, the event is removed from the associated event cache.
In this application, this handler is also used for pointercancel
, pointerleave
and pointerout
events.
js
function pointerupHandler(ev) {
if (logEvents) {
log(ev.type, ev);
}
// Remove this touch point from the cache and reset the target's
// background and border
removeEvent(ev);
updateBackground(ev);
ev.target.style.border = "1px solid black";
}
Application UI
The application uses <div>
elements for the touch areas and provides buttons to enable logging and to clear the log.
To prevent the browser's default touch behavior from overriding this application's pointer handling, the touch-action
property is applied to the <body>
element.
html
<body onload="init();" style="touch-action:none">
<div id="target1">Tap, Hold or Swipe me 1</div>
<div id="target2">Tap, Hold or Swipe me 2</div>
<div id="target3">Tap, Hold or Swipe me 3</div>
<!-- UI for logging/debugging -->
<button id="log" onclick="enableLog(event);">Start/Stop event logging</button>
<button id="clearlog" onclick="clearLog(event);">Clear the log</button>
<p></p>
<output></output>
</body>
Miscellaneous functions
These functions support the application but aren't directly involved with the event flow.
Cache management
These functions manage the global event caches evCache1
, evCache2
and evCache3
.
js
function getCache(ev) {
// Return the cache for this event's target element
switch (ev.target.id) {
case "target1":
return evCache1;
case "target2":
return evCache2;
case "target3":
return evCache3;
default:
log("Error with cache handling", ev);
}
}
function pushEvent(ev) {
// Save this event in the target's cache
const evCache = getCache(ev);
evCache.push(ev);
}
function removeEvent(ev) {
// Remove this event from the target's cache
const evCache = getCache(ev);
const index = evCache.findIndex(
(cachedEv) => cachedEv.pointerId === ev.pointerId
);
evCache.splice(index, 1);
}
Update background color
The background color of the touch areas will change as follows: no active touches is white
; one active touch is yellow
; two simultaneous touches is pink
and three or more simultaneous touches is lightblue
.
js
function updateBackground(ev) {
// Change background color based on the number of simultaneous touches/pointers
// currently down:
// white - target element has no touch points i.e. no pointers down
// yellow - one pointer down
// pink - two pointers down
// lightblue - three or more pointers down
const evCache = getCache(ev);
switch (evCache.length) {
case 0:
// Target element has no touch points
ev.target.style.background = "white";
break;
case 1:
// Single touch point
ev.target.style.background = "yellow";
break;
case 2:
// Two simultaneous touch points
ev.target.style.background = "pink";
break;
default:
// Three or more simultaneous touches
ev.target.style.background = "lightblue";
}
}
Event logging
These functions are used send to event activity to the application window (to support debugging and learning about the event flow).
js
// Log events flag
let logEvents = false;
function enableLog(ev) {
logEvents = !logEvents;
}
function log(name, ev) {
const o = document.getElementsByTagName("output")[0];
const s =
`${name}:<br>` +
` pointerID = ${ev.pointerId}<br>` +
` pointerType = ${ev.pointerType}<br>` +
` isPrimary = ${ev.isPrimary}`;
o.innerHTML += `${s}<br>`;
}
function clearLog(event) {
const o = document.getElementsByTagName("output")[0];
o.innerHTML = "";
}