An Introduction to Pointer Events
Majority of the web applications are designed for mouse input. They handle input through Mouse Events (mouseup, mousedown, mousemove & other mouse events).
Web applications wanting to handle mobile devices use Touch Events (touchstart, touchup, touchmove). But in addition to handling touch, they must handle mouse input as well. So to do the same job, they have to duplicate the code or bring an unnecessary if-else to handle both mouse and touch.
With time browser vendors realized this inefficiency and have brought Pointer Events to solve this. With Pointer Events you can handle both mouse and touch with a single shot, without any special case handling for mouse and touch !
To demonstrate how Pointer Events results in writing less code, I'll show a simple application.
Demonstrating the Advantage Using a Simple Example - Drawing on Canvas
Let us take a simple web application through which we can draw on <canvas>. To draw on the canvas, we first press the mouse (mousedown event) and then move the mouse (series of mousemove events). As we move the mouse, a line is drawn on the canvas between the current and the previous mouse position. When we release the mouse (mouseup event), drawing stops.
We will demonstrate this application using Mouse Events, Touch Events and finally combining both with Pointer Events.
Case 1 - Using only Mouse Events
<canvas id="paint-canvas" width="300" height="250"></canvas>
#paint-canvas {
border: 1px solid rgba(0,0,0,0.15);
}
var _CANVAS = $("#paint-canvas").get(0),
_CTX = _CANVAS.getContext("2d"),
_DRAGGING_STARTED = 0,
_LAST_MOUSEMOVE_POSITION = { x: null, y: null },
_CANVAS_OFFSET = $("#paint-canvas").offset();
/* On mousedown paiting starts. The clicked mouse position is saved */
$('#paint-canvas').on('mousedown', function(e) {
_DRAGGING_STARTED = 1;
_LAST_MOUSEMOVE_POSITION = { x: e.pageX - _CANVAS_OFFSET.left, y: e.pageY - _CANVAS_OFFSET.top };
});
/* On mouseup painting stops */
$('#paint-canvas').on('mouseup', function(e) {
_DRAGGING_STARTED = 0;
});
/* On mouseout painting stops */
$('#paint-canvas').on('mouseout', function(e) {
_DRAGGING_STARTED = 0;
});
/* With each mousemove a line is drawn between the current and previous mouse position. Mouse position is repeatedly saved */
$('#paint-canvas').on('mousemove', function(e) {
if(_DRAGGING_STARTED == 1) {
var current_mouse_position = { x: e.pageX - _CANVAS_OFFSET.left, y: e.pageY - _CANVAS_OFFSET.top };
_CTX.beginPath();
_CTX.moveTo(_LAST_MOUSEMOVE_POSITION.x, _LAST_MOUSEMOVE_POSITION.y);
_CTX.lineTo(current_mouse_position.x, current_mouse_position.y);
_CTX.lineWidth = 2;
_CTX.strokeStyle = "#000000";
_CTX.stroke();
_CTX.closePath();
_LAST_MOUSEMOVE_POSITION = current_mouse_position;
}
});
Case 2 - Using only Touch Events
For mobile devices we use touchstart instead of mousedown, touchend instead of mouseup and touchmove instead of mousemove. Mobile devices have no alternate for mouseout, and anyways there is no need to track this event currently.
A very important thing is to set touch-action CSS property to none when dealing with touch. This will ensure that the browser does not handle default touch events on its own. (In mobile devices there are a LOT of default touch events happening, some of those even cannot be suppressed with preventDefault. More information on Preventing the touch default)
In Mouse Events you can get the position of the mouse with pageX and pageY properties. But when dealing with touch, you cannot get the position in the same way. You can get the touch points through targetTouches property of the event object. This property holds an array that contains all the active touch points on the current DOM element. (Remember mobiles can be multi-touch). After you figure out the current touch point from this array, you can get the position of that touch point.
I've not used multi-touch in this example, so I'm just using the first touch point (0th index element of the targetTouches array).
Also jQuery does not pass targetTouches property into its event object (jQuery has its own event object that is different from the original event object passed by Javascript). So we have to access the original event object through the originalEvent property of the jQuery event object.
<canvas id="paint-canvas" width="300" height="250"></canvas>
#paint-canvas {
border: 1px solid rgba(0,0,0,0.15);
touch-action: none;
}
var _CANVAS = $("#paint-canvas").get(0),
_CTX = _CANVAS.getContext("2d"),
_DRAGGING_STARTED = 0,
_LAST_MOUSEMOVE_POSITION = { x: null, y: null },
_CANVAS_OFFSET = $("#paint-canvas").offset();
/* On touchstart paiting starts. The touched position is saved */
$('#paint-canvas').on('touchstart', function(e) {
e = e.originalEvent;
_DRAGGING_STARTED = 1;
_LAST_MOUSEMOVE_POSITION = { x: e.targetTouches[0].pageX - _CANVAS_OFFSET.left, y: e.targetTouches[0].pageY - _CANVAS_OFFSET.top };
});
/* On touchend painting stops */
$('#paint-canvas').on('touchend', function(e) {
_DRAGGING_STARTED = 0;
});
/* With each touchmove a line is drawn between the current and previous touch position. Touch position is repeatedly saved */
$('#paint-canvas').on('touchmove', function(e) {
if(_DRAGGING_STARTED == 1) {
e = e.originalEvent;
var current_mouse_position = { x: e.targetTouches[0].pageX - _CANVAS_OFFSET.left, y: e.targetTouches[0].pageY - _CANVAS_OFFSET.top };
_CTX.beginPath();
_CTX.moveTo(_LAST_MOUSEMOVE_POSITION.x, _LAST_MOUSEMOVE_POSITION.y);
_CTX.lineTo(current_mouse_position.x, current_mouse_position.y);
_CTX.lineWidth = 2;
_CTX.strokeStyle = "#000000";
_CTX.stroke();
_CTX.closePath();
_LAST_MOUSEMOVE_POSITION = current_mouse_position;
}
});
See the amount of extra work we have to do, in order to perform the same thing in mobile devices. This is certainly an inefficiency.
Case 3 - Combining Mouse & Touch By using Both Type of Events
We can combine both mouse and touch events by checking whether the event comes from a mouse or touch, and changing the code appropriately. The application works, but the extra work seems unnecessary. Plus there will be extra effort in future code maintenance.
$('#paint-canvas').on('mousedown touchstart', function(e) {
e = e.originalEvent;
_DRAGGING_STARTED = 1;
if(e.type == 'touchstart')
_LAST_MOUSEMOVE_POSITION = { x: e.targetTouches[0].pageX - _CANVAS_OFFSET.left, y: e.targetTouches[0].pageY - _CANVAS_OFFSET.top };
else
_LAST_MOUSEMOVE_POSITION = { x: e.pageX - _CANVAS_OFFSET.left, y: e.pageY - _CANVAS_OFFSET.top };
});
$('#paint-canvas').on('mouseup touchend', function(e) {
_DRAGGING_STARTED = 0;
});
$('#paint-canvas').on('mouseout', function(e) {
_DRAGGING_STARTED = 0;
});
$('#paint-canvas').on('mousemove touchmove', function(e) {
if(_DRAGGING_STARTED == 1) {
e = e.originalEvent;
var current_mouse_position;
if(e.type == 'touchmove')
current_mouse_position = { x: e.targetTouches[0].pageX - _CANVAS_OFFSET.left, y: e.targetTouches[0].pageY - _CANVAS_OFFSET.top };
else
current_mouse_position = { x: e.pageX - _CANVAS_OFFSET.left, y: e.pageY - _CANVAS_OFFSET.top };
_CTX.beginPath();
_CTX.moveTo(_LAST_MOUSEMOVE_POSITION.x, _LAST_MOUSEMOVE_POSITION.y);
_CTX.lineTo(current_mouse_position.x, current_mouse_position.y);
_CTX.lineWidth = 2;
_CTX.strokeStyle = "#000000";
_CTX.stroke();
_CTX.closePath();
_LAST_MOUSEMOVE_POSITION = current_mouse_position;
}
});
Case 4 - Combining Mouse & Touch By Using Pointer Events
Now comes the Pointer Events !
Use pointerdown, pointerup, pointerout and pointermove events and it will work for both desktops and mobiles without any special case handling.
<canvas id="paint-canvas" width="300" height="250"></canvas>
#paint-canvas {
border: 1px solid rgba(0,0,0,0.15);
touch-action: none;
}
$('#paint-canvas').on('pointerdown', function(e) {
_DRAGGING_STARTED = 1;
_LAST_MOUSEMOVE_POSITION = { x: e.pageX - _CANVAS_OFFSET.left, y: e.pageY - _CANVAS_OFFSET.top };
});
$('#paint-canvas').on('pointerup', function(e) {
_DRAGGING_STARTED = 0;
});
$('#paint-canvas').on('pointerout', function(e) {
_DRAGGING_STARTED = 0;
});
$('#paint-canvas').on('pointermove', function(e) {
if(_DRAGGING_STARTED == 1) {
var current_mouse_position = { x: e.pageX - _CANVAS_OFFSET.left, y: e.pageY - _CANVAS_OFFSET.top };
_CTX.beginPath();
_CTX.moveTo(_LAST_MOUSEMOVE_POSITION.x, _LAST_MOUSEMOVE_POSITION.y);
_CTX.lineTo(current_mouse_position.x, current_mouse_position.y);
_CTX.lineWidth = 2;
_CTX.strokeStyle = "#000000";
_CTX.stroke();
_CTX.closePath();
_LAST_MOUSEMOVE_POSITION = current_mouse_position;
}
});
Indeed very useful !
Browser Compatibility
Pointer Events are supported in all browsers except Safari. But it will be implemented in Safari version 13.