pointer events improvements
This commit is contained in:
parent
1b1a2c4b66
commit
9c5388cd57
2 changed files with 83 additions and 36 deletions
|
@ -163,7 +163,7 @@ Pointer capturing is a special feature of pointer events.
|
|||
The idea is very simple, but may seem quite odd at first, as nothing like that exists for any other event type.
|
||||
|
||||
The main method is:
|
||||
- `elem.setPointerCapture(pointerId)` - binds events with the given `pointerId` to `elem`. After the call all pointer events with the same `pointerId` will have `elem` as the target (as if happened on `elem`), no matter where in document they really happened.
|
||||
- `elem.setPointerCapture(pointerId)` -- binds events with the given `pointerId` to `elem`. After the call all pointer events with the same `pointerId` will have `elem` as the target (as if happened on `elem`), no matter where in document they really happened.
|
||||
|
||||
In other words, `elem.setPointerCapture(pointerId)` retargets all subsequent events with the given `pointerId` to `elem`.
|
||||
|
||||
|
@ -172,29 +172,43 @@ The binding is removed:
|
|||
- automatically when `elem` is removed from the document,
|
||||
- when `elem.releasePointerCapture(pointerId)` is called.
|
||||
|
||||
Now what is it good for? It's time to see a real-life example.
|
||||
|
||||
**Pointer capturing can be used to simplify drag'n'drop kind of interactions.**
|
||||
|
||||
As an example, let's recall how one can implement a custom slider, described in the <info:mouse-drag-and-drop>.
|
||||
Let's recall how one can implement a custom slider, described in the <info:mouse-drag-and-drop>.
|
||||
|
||||
We make a slider element with the strip and the "runner" (`thumb`) inside it.
|
||||
We can make a `slider` element to represent the strip and the "runner" (`thumb`) inside it:
|
||||
|
||||
Then it works like this:
|
||||
```html
|
||||
<div class="slider">
|
||||
<div class="thumb"></div>
|
||||
</div>
|
||||
```
|
||||
|
||||
1. The user presses on the slider `thumb` - `pointerdown` triggers.
|
||||
2. Then they move the pointer - `pointermove` triggers, and we move the `thumb` along.
|
||||
- ...As the pointer moves, it may leave the slider `thumb`: go above or below it. The `thumb` should move strictly horizontally, remaining aligned with the pointer.
|
||||
With styles, it looks like this:
|
||||
|
||||
So, to track all pointer movements, including when it goes above/below the `thumb`, we had to assign `pointermove` event handler on the whole `document`.
|
||||
[iframe src="slider-html" height=40 edit]
|
||||
|
||||
That solution looks a bit "dirty". One of the problems is that pointer movements around the document may cause side effects, trigger other event handlers, totally not related to the slider.
|
||||
<p></p>
|
||||
|
||||
Pointer capturing provides a means to bind `pointermove` to `thumb` and avoid any such problems:
|
||||
And here's the working logic, as it was described, after replacing mouse events with similar pointer events:
|
||||
|
||||
1. The user presses on the slider `thumb` -- `pointerdown` triggers.
|
||||
2. Then they move the pointer -- `pointermove` triggers, and our code moves the `thumb` element along.
|
||||
- ...As the pointer moves, it may leave the slider `thumb` element, go above or below it. The `thumb` should move strictly horizontally, remaining aligned with the pointer.
|
||||
|
||||
In the mouse event based solution, to track all pointer movements, including when it goes above/below the `thumb`, we had to assign `mousemove` event handler on the whole `document`.
|
||||
|
||||
That's not a cleanest solution, though. One of the problems is that when a user moves the pointer around the document, it may trigger event handlers (such as `mouseover`) on some other elements, invoke totally unrelated UI functionality, and we don't want that.
|
||||
|
||||
This is the place where `setPointerCapture` comes into play.
|
||||
|
||||
- We can call `thumb.setPointerCapture(event.pointerId)` in `pointerdown` handler,
|
||||
- Then future pointer events until `pointerup/cancel` will be retargeted to `thumb`.
|
||||
- When `pointerup` happens (dragging complete), the binding is removed automatically, we don't need to care about it.
|
||||
|
||||
So, even if the user moves the pointer around the whole document, events handlers will be called on `thumb`. Besides, coordinate properties of the event objects, such as `clientX/clientY` will still be correct - the capturing only affects `target/currentTarget`.
|
||||
So, even if the user moves the pointer around the whole document, events handlers will be called on `thumb`. Nevertheless, coordinate properties of the event objects, such as `clientX/clientY` will still be correct - the capturing only affects `target/currentTarget`.
|
||||
|
||||
Here's the essential code:
|
||||
|
||||
|
@ -202,12 +216,20 @@ Here's the essential code:
|
|||
thumb.onpointerdown = function(event) {
|
||||
// retarget all pointer events (until pointerup) to thumb
|
||||
thumb.setPointerCapture(event.pointerId);
|
||||
};
|
||||
|
||||
thumb.onpointermove = function(event) {
|
||||
// start tracking pointer moves
|
||||
thumb.onpointermove = function(event) {
|
||||
// moving the slider: listen on the thumb, as all pointer events are retargeted to it
|
||||
let newLeft = event.clientX - slider.getBoundingClientRect().left;
|
||||
thumb.style.left = newLeft + 'px';
|
||||
};
|
||||
|
||||
// on pointer up finish tracking pointer moves
|
||||
thumb.onpointerup = function(event) {
|
||||
thumb.onpointermove = null;
|
||||
thumb.onpointerup = null;
|
||||
// ...also process the "drag end" if needed
|
||||
};
|
||||
};
|
||||
|
||||
// note: no need to call thumb.releasePointerCapture,
|
||||
|
@ -218,15 +240,27 @@ thumb.onpointermove = function(event) {
|
|||
The full demo:
|
||||
|
||||
[iframe src="slider" height=100 edit]
|
||||
|
||||
<p></p>
|
||||
|
||||
In the demo, there's also an additional element with `onmouseover` handler showing the current date.
|
||||
|
||||
Please note: while you're dragging the thumb, you may hover over this element, and its handler *does not* trigger.
|
||||
|
||||
So the dragging is now free of side effects, thanks to `setPointerCapture`.
|
||||
```
|
||||
|
||||
|
||||
|
||||
At the end, pointer capturing gives us two benefits:
|
||||
1. The code becomes cleaner as we don't need to add/remove handlers on the whole `document` any more. The binding is released automatically.
|
||||
2. If there are any `pointermove` handlers in the document, they won't be accidentally triggered by the pointer while the user is dragging the slider.
|
||||
2. If there are other pointer event handlers in the document, they won't be accidentally triggered by the pointer while the user is dragging the slider.
|
||||
|
||||
### Pointer capturing events
|
||||
|
||||
There are two associated pointer events:
|
||||
There's one more thing to mention here, for the sake of completeness.
|
||||
|
||||
There are two events associated with pointer capturing:
|
||||
|
||||
- `gotpointercapture` fires when an element uses `setPointerCapture` to enable capturing.
|
||||
- `lostpointercapture` fires when the capture is released: either explicitly with `releasePointerCapture` call, or automatically on `pointerup`/`pointercancel`.
|
||||
|
|
|
@ -5,19 +5,30 @@
|
|||
<div class="thumb"></div>
|
||||
</div>
|
||||
|
||||
<p style="border:1px solid gray" onmousemove="this.textContent = new Date()">Mouse over here to see the date</p>
|
||||
|
||||
<script>
|
||||
let thumb = slider.querySelector('.thumb');
|
||||
let shiftX;
|
||||
|
||||
thumb.onpointerdown = function(event) {
|
||||
function onThumbDown(event) {
|
||||
event.preventDefault(); // prevent selection start (browser action)
|
||||
|
||||
shiftX = event.clientX - thumb.getBoundingClientRect().left;
|
||||
|
||||
thumb.setPointerCapture(event.pointerId);
|
||||
|
||||
thumb.onpointermove = onThumbMove;
|
||||
|
||||
thumb.onpointerup = event => {
|
||||
// dragging finished, no need to track pointer any more
|
||||
// ...any other "drag end" logic here...
|
||||
thumb.onpointermove = null;
|
||||
thumb.onpointerup = null;
|
||||
}
|
||||
};
|
||||
|
||||
thumb.onpointermove = function(event) {
|
||||
function onThumbMove(event) {
|
||||
let newLeft = event.clientX - shiftX - slider.getBoundingClientRect().left;
|
||||
|
||||
// if the pointer is out of slider => adjust left to be within the bounaries
|
||||
|
@ -32,6 +43,8 @@
|
|||
thumb.style.left = newLeft + 'px';
|
||||
};
|
||||
|
||||
thumb.onpointerdown = onThumbDown;
|
||||
|
||||
thumb.ondragstart = () => false;
|
||||
|
||||
</script>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue