|
| 1 | +# Pointer events |
| 2 | + |
| 3 | +Pointer events is a modern way to handle input from a variety of pointing devices, such as a mouse, a pen/stylus, a touchscreen and so on. |
| 4 | + |
| 5 | +## The brief history |
| 6 | + |
| 7 | +Let's make a small overview, so that you understand the general picture and the place of Pointer Events among other event types. |
| 8 | + |
| 9 | +- Long ago, in the past, there existed only mouse events. |
| 10 | + |
| 11 | + Then touch devices appeared. For the old code to work, they also generate mouse events. For instance, tapping generates `mousedown`. But mouse events were not good enough, as touch devices are more powerful in many aspects. For example, it's possible to touch multiple points at once, and mouse events don't have any properties for that. |
| 12 | + |
| 13 | +- So touch events were introduced, such as `touchstart`, `touchend`, `touchmove`, that have touch-specific properties (we don't cover them in details here, because pointer events are event better). |
| 14 | + |
| 15 | + Still, it wasn't enough, as there are many other devices, such as pens, that have their own features. Also, writing a code that listens both touch and mouse events was cumbersome. |
| 16 | + |
| 17 | +- To solve these issues, the new standard Pointer Events was introduced. It provides a single set of events for all kinds of pointing devices. |
| 18 | + |
| 19 | +As of now, [Pointer Events Level 2](https://www.w3.org/TR/pointerevents2/) specification is supported in all major browsers, while the [Pointer Events Level 3](https://w3c.github.io/pointerevents/) is in the works. Unless you code for Internet Explorer 10- and Safari 12-, there's no point in using mouse or touch events any more. We can switch to pointer events. |
| 20 | + |
| 21 | +That said, there are important peculiarities, one should know them to use them correctly and avoid extra surprises. We'll pay attention to them in this article. |
| 22 | + |
| 23 | +## Pointer event types |
| 24 | + |
| 25 | +Pointer events are named similar to mouse events: |
| 26 | + |
| 27 | +| Pointer Event | Mouse event | |
| 28 | +|---------------|-------------| |
| 29 | +| `pointerdown` | `mousedown` | |
| 30 | +| `pointerup` | `mouseup` | |
| 31 | +| `pointermove` | `mousemove` | |
| 32 | +| `pointerover` | `mouseover` | |
| 33 | +| `pointerout` | `mouseout` | |
| 34 | +| `pointerenter` | `mouseenter` | |
| 35 | +| `pointerleave` | `mouseleave` | |
| 36 | +| `pointercancel` | - | |
| 37 | +| `gotpointercapture` | - | |
| 38 | +| `lostpointercapture` | - | |
| 39 | + |
| 40 | +As we can see, for every `mouse<event>`, there's a `pointer<event>` that plays a similar role. Also there are 3 additional pointer events that don't have a corresponding `mouse...` counterpart, we'll soon explain about them. |
| 41 | + |
| 42 | +## Pointer event properties |
| 43 | + |
| 44 | +Pointer events have the same properties as mouse events, such as `clientX/Y`, `target` etc, plus some extra: |
| 45 | + |
| 46 | +- `pointerId` - the unique identifier of the pointer causing the event. |
| 47 | +- `pointerType` - the pointing device type, must be a string, one of: "mouse", "pen" or "touch". Can use this to react differently on these device types. |
| 48 | +- `isPrimary` - `true` for the primary pointer, used to handle multi-touch, explained below. |
| 49 | + |
| 50 | +For pointers that have a changing contact area, e.g. a finger on the touchpad, these can be useful: |
| 51 | + |
| 52 | +- `width` - the width of of the area where the pointer touches the device. Where unsupported, e.g. for mouse it's always `1`. |
| 53 | +- `height` - the height of of the area where the pointer touches the device. Where unsupported, always `1`. |
| 54 | + |
| 55 | +Other properties (rarely used): |
| 56 | +- `pressure` - the pressure of the pointer tip, in range from 0 to 1. For devices that don't support pressure must be either `0.5` (pressed) or `0`. |
| 57 | +- `tangentialPressure` - the normalized tangential pressure. |
| 58 | +- `tiltX`, `tiltY`, `twist` - pen-specific properties that describe how the pen is positioned relative the surface. |
| 59 | + |
| 60 | +The last set of properties is supported by few special devices, you can find the details in the [specification](https://w3c.github.io/pointerevents/#pointerevent-interface) if ever needed. |
| 61 | + |
| 62 | +Let's see some examples where these properties may be helpful. |
| 63 | + |
| 64 | +## Multi-touch |
| 65 | + |
| 66 | +Phones and tablets, other devices that employ touchscreens, usually support multi-touch: a user can touch them in several places at once. |
| 67 | + |
| 68 | +Pointer Events allow to handle multi-touch with the help of `pointerId` and `isPrimary` properties. |
| 69 | + |
| 70 | +What happens when we touch a screen at one place, and then put another finger somewhere else? |
| 71 | + |
| 72 | +1. At the first touch we get `pointerdown` with `isPrimary=true` and some `pointerId`. |
| 73 | +2. For the second finger and further touches we get `pointerdown` with `isPrimary=false` and a different `pointerId`. |
| 74 | + |
| 75 | +Please note: there's a `pointerId` for each pointer - a touching finger. If we use 5 fingers to simultaneously touch the screen, we have 5 `pointerdown` events with respective coordinates and different `pointerId`. |
| 76 | + |
| 77 | +The events associated with the first finger always have `isPrimary=true`. |
| 78 | + |
| 79 | +Whether we move and then detouch a finger, we get `pointermove` and `pointerup` events with the same `pointerId` as we had in `pointerdown`. So we can track multiple touching fingers using their `pointerId`. |
| 80 | + |
| 81 | +```online |
| 82 | +Here's the demo that logs `pointerdown` and `pointerup` events: |
| 83 | +
|
| 84 | +[iframe src="multitouch" edit height=200] |
| 85 | +
|
| 86 | +Please note: you must be using a touchscreen device, such as a phone or a tablet to actually see the difference. For single-touch devices, such as a mouse, there'll be always same `pointerId` with `isPrimary=true`. |
| 87 | +``` |
| 88 | + |
| 89 | +## Event: pointercancel |
| 90 | + |
| 91 | +This event fires when there's an ongoing pointer interaction, and then something happens that causes it to be aborted, so that no more pointer events are generated. |
| 92 | + |
| 93 | +Such causes are: |
| 94 | +- The pointer device hardware was disabled. |
| 95 | +- The device orientation changed (tablet rotated). |
| 96 | +- The browser decided to handle the interaction on its own, considering it a mouse gesture or zoom-and-pan action or something else. |
| 97 | + |
| 98 | +The last reason is the most common and important one. So we'll demonstrate it on a practical example. |
| 99 | + |
| 100 | +Let's say we're impelementing drag'n'drop for a ball, just as in the beginning of the article <info:mouse-drag-and-drop>. |
| 101 | + |
| 102 | +Here are the user actions and corresponding events: |
| 103 | + |
| 104 | +1) The user presses the mouse button on an image |
| 105 | + - `pointerdown` event fires |
| 106 | +2) Then they start dragging the image |
| 107 | + - `pointermove` fires, maybe several times |
| 108 | +3) The browser has native drag'n'drop support for images, that kicks in and takes over the drag'n'drop, thus generating `pointercancel` event. |
| 109 | + - The browser now hangles drag'n'drop of the image. |
| 110 | + - No more `pointermove` events. Our code doesn't work any more! |
| 111 | + |
| 112 | +The browser "hijacks" the interaction: `pointercancel` fires and no more `pointermove` events are generated! |
| 113 | + |
| 114 | +```online |
| 115 | +Here's the demo with pointer events (only `up/down`, `move` and `cancel`) logged in the textarea: |
| 116 | +
|
| 117 | +[iframe src="ball" height=240 edit] |
| 118 | +``` |
| 119 | + |
| 120 | +**Prevent default browser actions to avoid `pointercancel`.** |
| 121 | + |
| 122 | +We need to do two things: |
| 123 | + |
| 124 | +1. Prevent drag'n'drop, e.g. by setting `ball.ondragstart = () => false`, just as described in the article <info:mouse-drag-and-drop>. |
| 125 | +2. For touch devices, there are also touch-related browser actions. We'll have problems with them too, so we should prevent them by setting `#ball { touch-action: none }` in CSS. That's required for our code to work on touch devices. |
| 126 | + |
| 127 | +Then the events work as intended, the browser doesn't hijack the process and no `pointercancel` triggers. |
| 128 | + |
| 129 | +```online |
| 130 | +This demo adds these lines: |
| 131 | +
|
| 132 | +[iframe src="ball-2" height=240 edit] |
| 133 | +
|
| 134 | +As you can see, there's no `pointercancel` any more. |
| 135 | +``` |
| 136 | + |
| 137 | +Now we can add the code to actually move the ball, and our drag'n'drop will work for mouse devices and touch devices. |
| 138 | + |
| 139 | +## Pointer capturing |
| 140 | + |
| 141 | +Pointer capturing is an interesting feature of pointer events. |
| 142 | + |
| 143 | +The idea is that we can "bind" all events with a particular `pointerId` to a given element. Then all subsequent events with the same `pointerId` will be retargeted to the same element. |
| 144 | + |
| 145 | +The related methods are: |
| 146 | +- `elem.setPointerCapture(pointerId)` - binds the given `pointerId` to `elem`. |
| 147 | +- `elem.releasePointerCapture(pointerId)` - unbinds the given `pointerId` from `elem`. |
| 148 | + |
| 149 | +Such binding doesn't hold long! It's automatically removed after `pointerup` or `pointercancel` events, or when the target `elem` is removed from the document. |
| 150 | + |
| 151 | +**Pointer capturing is used to simplify drag'n'drop kind of interactions.** |
| 152 | + |
| 153 | +We've already met the problem when making a custom slider in the article <info:mouse-drag-and-drop>. |
| 154 | + |
| 155 | +1) First, the user should press `pointerdown` on the slider thumb to start dragging it. |
| 156 | +2) ...But then the pointer may leave the slider and go elsewhere: below or over it, but `pointermove` events should still be tracked, and the thumb moved. |
| 157 | + |
| 158 | +Previously, to handle `pointermove` events that happen outside of the slider, we used `pointermove` events on the whole `document`. |
| 159 | + |
| 160 | +Pointer capturing provides an alternative solution: we can `thumb.setPointerCapture(event.pointerId)` in `pointerdown` handler, and then all future pointer events until `pointerup` will be retarteted to `thumb`. |
| 161 | + |
| 162 | +That is: events handlers on `thumb` will be called, and `event.target` will always be `thumb`, even if the user moves their pointer around the whole document. |
| 163 | + |
| 164 | +Here's the essential code: |
| 165 | + |
| 166 | +```js |
| 167 | +thumb.onpointerdown = function(event) { |
| 168 | + // retarget all pointer events (until pointerup) to me |
| 169 | + thumb.setPointerCapture(event.pointerId); |
| 170 | +}; |
| 171 | + |
| 172 | +thumb.onpointermove = function(event) { |
| 173 | + // move the slider |
| 174 | + let newLeft = event.clientX - slider.getBoundingClientRect().left; |
| 175 | + |
| 176 | + thumb.style.left = newLeft + 'px'; |
| 177 | +}; |
| 178 | +// no need to call thumb.releasePointerCapture, happens on pointerup automatically |
| 179 | +``` |
| 180 | + |
| 181 | +```online |
| 182 | +The full demo: |
| 183 | +
|
| 184 | +[iframe src="slider" height=100 edit] |
| 185 | +``` |
| 186 | + |
| 187 | +As a summary: the code becomes cleaner as we don't need to add/remove handlers on the whole `document` any more. |
| 188 | + |
| 189 | +There are two associated pointer events: |
| 190 | + |
| 191 | +- `gotpointercapture` fires when an element uses `setPointerCapture` to enable capturing. |
| 192 | +- `lostpointercapture` fires when the capture is released: either explicitly with `releasePointerCapture` call, or automatically on `pointerup`/`pointercancel`. |
| 193 | + |
| 194 | +## Summary |
| 195 | + |
| 196 | +Pointer events allow to handle mouse, touch and pen events simultaneously. |
| 197 | + |
| 198 | +Pointer events extend mouse events. We can replace `mouse` with `pointer` in event names and expect our code to continue working for mouse, with better support for other device types. |
| 199 | + |
| 200 | +Remember to set `touch-events: none` in CSS for elements, otherwise the browser hijacks many types of touch interactions and pointer events won't be generated. |
| 201 | + |
| 202 | +Additional abilities of Pointer events are: |
| 203 | + |
| 204 | +- Multi-touch support using `pointerId` and `isPrimary`. |
| 205 | +- Device-specific properties, such as `pressure`, `width/height` and others. |
| 206 | +- Pointer capturing: we can retarget all pointer events to a specific element until `pointerup`/`pointercancel`. |
| 207 | + |
| 208 | +As of now, pointer events are supported in all major browsers, so we can safely switch to them, if IE10- and Safari 12- is not needed. And even with those browsers, there are polyfills that enable the support of pointer events. |
0 commit comments