February 1, 2020
If you want interactive pages, then you'll probably need to handle various types of events, like mouse events, but mobile phones and iPads don't have mice. Here's how to make something draggable, as in "click-and-drag the mouse," on a touch device. To fully follow what is demonstrated, it will be helpful if you can open this page on a computer with a mouse, and on a touch device.
Below is a small canvas with a square drawn inside. Click on the square with the mouse to move the square around within the canvas. Pretty exciting, I know.
Yes, the square is blurry, but that will be addressed on a different page. A non-blurry implementation, based on SVG, is given here .
If you try to drag with your finger on a touch device, this little experiment won't work because the canvas above is listening for the wrong kinds of events. You can read the underlying contents (the HTML and JavaScript) of this page to see every detail of the code, but the important point is that the canvas listens for only these events:
theCanvas.addEventListener("mousedown",doMouseDown,false); theCanvas.addEventListener("mouseup",doMouseUp,false); theCanvas.addEventListener("mousemove",doMouseMove,false); theCanvas.addEventListener("mouseout",doMouseOut,false);
What's happening with these events is this.
mousedown
event. When doMouseDown
is called,
the draggingSquare
flag is set, and the point at which the mouse was pressed is noted.
doMouseMove
determines how much the
mouse has moved relative to where it was initially pressed. This determines
an offset to where the square should be redrawn.
doMouseUp
turns off the
draggingSquare
flag.
doMouseOut
also turns off the draggingSquare
flag.
Anyone who's done much graphics programming has written code like this many (many!) times. To see all of the details, look at the page source. I'm not using any kind of content management system, so the source is human-readable, with comments.
Handling these mouse events works on a non-touch device, but they don't really make sense for a touch screen.
The fix is pretty easy. Touch devices have three events that are analagous to the
corresponding mouse events: touchstart
, touchend
and
touchmove
can be used in a way similar to mousedown
,
mouseup
and mousemove
.
To make the square draggable on both touch and mouse devices, add the three touch events to the four mouse events that are already being handled.
Here are the details of what was changed in the code. First, add these additional listeners.
theCanvas.addEventListener("touchstart",doMouseDown,false); theCanvas.addEventListener("touchend",doMouseUp,false); theCanvas.addEventListener("touchmove",doMouseMove,false);
The original event listeners, doMouseDown
, etc., still work, with
one modification. On touch devices, the listeners will receive touch events
instead of mouse events, and touch events have a different type, with different
fields. Whereas a mouse event has clientX
and clientY
fields
for where the mouse was clicked, a touch event does not. Instead, a touch event holds
an array, touches
, that holds multiple sets of coordinates, one for each
"touch" involved (i.e., multiple fingers).
Each time the listener code is called, it needs to access the mouse or finger location, and the two cases need to be distinguished. There are various ways this could be done, but one way is based on looking at what the event variable holds, like this.
if ('clientX' in theEvent) { // Must be a mouse event, so use theEvent.clientX and // the Event.clientY. } else { // Must be a touch event. We only care about the first // finger/touch, so use theEvent.touches[0].clientX and // theEvent.touches[0].clientY. }
Another issue is the fact that we don't want the browser to react to touches in the canvas – we are handling it. To prevent the browser from reacting to touches, the canvas needs two style settings:
touch-action: none; user-select: none;
Setting touch-action: none
means that the browser won't try to do
anything like pan or zoom the canvas in response to touches, and user-select: none
means that the browser won't allow the user to select the canvas (like for copy-and-paste).
Sharp-eyed readers might be wondering about the mouseout
event.
At one time, there was a touchleave
event meant to be much like a
mouseout
event, but it was deprecated and (as far as I can tell)
doesn't work on most browsers these days. If necessary, it's possible to replicate
the functionality of mouseout
/touchleave
in the
implementation of doMouseMove
, but I haven't done so. If you play
with the canvases above on a mouse device and on a touch device, you'll see that
dragging stops if the mouse leaves the canvas, but it doesn't stop on a touch device.
On a touch device, your finger can leave the canvas and come back to it, and
the "drag" continues; that doesn't work with a mouse. The reason the two cases are
different is because the doMouseOut
code can never be called on a
touch device.