Les événements de pointeur sont un moyen moderne de gérer les entrées dâune grande variété de périphérique de pointage, tel que les souris, les stylets, les écrans tactiles, etc.
Lâhistoire en bref
Réalisons un aperçu rapide, pour que vous compreniez lâidée générale et la place des événements de pointeur parmi les autres types dâévénement.
-
Autrefois, il y avait uniquement des événements de souris.
Puis, les appareils à écran tactile se sont généralisés, plus particulièrement les téléphones portables et les tablettes. Pour que les scripts existants continuent de fonctionner, ces appareils ont généré (et génèrent toujours) des événements de souris. Par exemple, tapoter sur un écran tactile génère un événement
mousedown. Ainsi, les appareils à écran tactile fonctionnaient bien avec les pages web.Mais les appareils à écran tactile ont plus de potentiel quâune souris. Par exemple, il est possible de cibler plusieurs endroits à la fois (âmulti-touchâ). Néanmoins, les événements de souris nâont pas les propriétés nécessaires pour gérer le multi-touch.
-
Ainsi, les événements tactiles ont été introduit, tels que
touchstart,touchend,touchmove, qui ont des propriétés tactiles spécifiques (nous ne les couvrirons pas en détails ici, car les événements de pointeur sont bien meilleur).Pourtant, cela nâa pas suffit, puisquâil existe beaucoup dâautres périphériques, tels que les stylets, qui ont leurs propres particularités. Ãgalement, écrire du code qui gérait à la fois les événements tactiles et de souris était fastidieux.
-
Pour résoudre ces problèmes, le nouveau standard Pointer Events a été introduit. Il fournit un ensemble dâévénements pour tout type de périphérique de pointage.
à ce jour, les spécifications Pointer Events Level 2 sont prises en charge dans tous les principaux navigateurs, tandis que les spécifications Pointer Events Level 3, plus récentes, sont en cours de rédaction et sont en grande partie compatible avec Pointer Events Level 2.
à moins que vous développiez pour de vieux navigateurs, tels quâInternet Explorer 10, Safari 12 ou antérieur, il est inutile dâutiliser les événements de souris ou tactiles â nous pouvons passer aux événements de pointeur.
Ainsi votre code fonctionnera aussi bien avec un périphérique tactile quâavec une souris.
Cela dit, il existe quelques particularités importantes à connaître pour se servir des événements de pointeur correctement et éviter les surprises. Nous mettrons lâaccent sur ces derniers dans cet article.
Les types dâévénement de pointeur
Les événements de pointeur sont nommés de façon similaire aux événements de souris:
| Ãvénement de pointeur | Ãvénement de souris équivalent |
|---|---|
pointerdown |
mousedown |
pointerup |
mouseup |
pointermove |
mousemove |
pointerover |
mouseover |
pointerout |
mouseout |
pointerenter |
mouseenter |
pointerleave |
mouseleave |
pointercancel |
- |
gotpointercapture |
- |
lostpointercapture |
- |
Comme nous pouvons le voir, pour chaque mouse<event>, il existe un pointer<event> jouant un rôle similaire. Il existe également 3 événements de pointeur supplémentaires qui nâont pas dâévénement mouse... équivalent. Nous les étudierons en détails bientôt.
mouse<event> par pointer<event> dans notre codeNous pouvons remplacer les événements mouse<event> par pointer<event> dans notre code et sâattendre à ce quâil continue de fonctionner correctement avec une souris.
La prise en charge des périphériques tactiles sâaméliorera aussi âcomme par magieâ, bien que nous ayons besoin de rajouter touch-action: none à certains endroits du CSS. Nous couvrirons ce sujet plus bas dans la partie sur lâévénement pointercancel.
Les propriétés de lâévénement de pointeur
Les événements de pointeur ont les mêmes propriétés que les événements de souris, telles que clientX/Y, target, etc, ainsi que dâautres:
-
pointerIdâ lâidentifiant unique du pointeur provoquant lâévénement.Généré par le navigateur. Nous permet de gérer plusieurs pointeurs, tels quâun écran tactile multi-touch muni dâun stylet (des exemples suivront).
-
pointerTypeâ le type de périphérique de pointage. Doit être une chaîne de caractère, parmi ceux-ci : âmouseâ, âpenâ ou âtouchâ.Nous pouvons utiliser cette propriété pour réagir différemment en fonction du type de pointeur.
-
isPrimaryâ esttruepour le pointeur principal (le premier doigt en multi-touch).
Certains périphériques de pointage mesurent la surface de contact et la pression appliquée, par exemple pour un doigt sur lâécran tactile. Il existe des propriétés supplémentaires pour cela:
widthâ la largeur de la zone du pointeur (par exemple un doigt) en contact avec lâappareil. Si incompatible, pour une souris par exemple, prend la valeur1.heightâ la hauteur de la zone du pointeur en contact avec lâappareil. Si incompatible, prend la valeur1.pressureâ la pression de lâextrémité du pointeur, prenant des valeurs comprises entre 0 et 1. Pour les appareils qui ne prennent pas en charge la pression, la valeur doit être soit0.5(pression appliquée) ou0.tangentialPressureâ la pression tangentielle normalisée.tiltX,tiltY,twistâ propriétés spécifiques au stylet qui décrivent la position relative du stylet par rapport à la surface.
Ces propriétés ne sont pas prises en charge par la plupart des appareils, et sont donc rarement utilisées. Vous trouverez plus de détails sur ces propriétés dans les spécifications si besoin.
Le multi-touch
Une des choses que les événements de souris ne prennent pas du tout en charge est le multi-touch: un utilisateur peut cibler plusieurs endroits en même temps sur lâécran de son téléphone portable ou de sa tablette, ou réaliser des gestes particuliers.
Les événements de pointeur permettent la gestion du multi-touch avec lâaide des propriétés pointerId et isPrimary.
Voila ce qui arrive lorsquâun utilisateur touche un écran tactile à un endroit, puis rajoute un second doigt à un autre endroit:
- Au contact du premier doigt:
pointerdownavecisPrimary=trueet unpointerId.
- Pour le deuxième doigt et les suivants (en considérant que le premier est toujours en contact avec lâécran):
pointerdownavecisPrimary=falseet unpointerIddifférent pour chaque doigt.
Remarque: le pointerId nâest pas attribué à lâensemble du périphérique, mais à chaque doigt en contact. Si nous utilisons 5 doigts simultanément pour toucher lâécran, nous avons 5 événements pointerdown, chacun avec ces coordonnées respectives et un pointerId différent.
Les événements associés au premier doigt ont toujours isPrimary=true.
Nous pouvons suivre plusieurs doigts en contact en utilisant leur pointerId. Quand lâutilisateur déplace un doigt et le déplace à nouveau, nous recevons des événements pointermove et pointerup avec un pointerId identique à celui de pointerdown.
Voici la démo qui consigne les événements pointerdown et pointerup:
Remarque: vous devez utiliser un appareil à écran tactile, tel quâun téléphone portable ou une tablette, pour voir la différence sur pointerId/isPrimary. Pour les périphériques single-touch, tels quâune souris, il y aura toujours le même pointerId avec isPrimary=true, pour tous les événements de pointeur.
Lâévénement pointercancel
Lâévénement pointercancel se déclenche quand une interaction de pointeur est en cours, et quâun événement provoquant son interruption se produit, de façon à ce que plus aucun événement de pointeur soit généré.
De tels événements sont:
- Le périphérique de pointage a été physiquement désactivé.
- Lâorientation de lâappareil a été modifié (pivotement de la tablette).
- Le navigateur a décidé de gérer lâinteraction lui-même, la considérant comme un mouvement de souris, une action de zoom et panorama ou autres.
Nous allons montrer le fonctionnement de pointercancel à lâaide dâun exemple pratique pour voir comment il nous impacte.
Supposons que nous mettions en place un glisser-déposer pour un ballon, comme au début de lâarticle Les évènements Glisser-Déposer de la souris.
Voici le flux dâactions de lâutilisateur et les événements correspondants:
- Lâutilisateur appuie sur lâimage pour commencer à la déplacer
- lâévénement
pointerdownse déclenche
- lâévénement
- Ensuite, il commence à déplacer le pointeur (en faisant ainsi glisser lâimage)
- lâévénement
pointerdownse déclenche, peut-être même plusieurs fois
- lâévénement
- Et là , surprise! Le navigateur prend nativement en charge le glisser-déposer dâimages, qui fait alors effet et prend le contrôle du processus de glisser-déposer, générant ainsi un événement
pointercancel.- Le navigateur gère maintenant seul le glisser-déposer de lâimage. Lâutilisateur peut même déplacer lâimage du ballon hors du navigateur, dans sa messagerie électronique ou son gestionnaire de fichier.
- Plus dâévénements
pointermovepour nous.
Ainsi, le problème est le âdétournementâ de lâinteraction par le navigateur: pointercancel se déclenche au début du processus de glisser-déposer, et plus aucun événement pointermove est généré.
Voici la démo du glisser-déposer avec consignation des événements de pointeur (uniquement up/down, move et cancel) dans la textarea :
Nous aimerions implémenter nous même le glisser-déposer, alors indiquons au navigateur de ne pas en prendre le contrôle.
Empêcher lâaction par défaut du navigateur afin dâéviter pointercancel.
Nous avons besoin de deux choses:
- Empêcher le glisser-déposer dâorigine de se produire:
- Nous pouvons faire cela en définissant
ball.ondragstart = () => false, comme décrit dans lâarticle Les évènements Glisser-Déposer de la souris. - Ceci fonctionne bien pour les événements de souris.
- Nous pouvons faire cela en définissant
- Pour les appareils tactiles, il existe dâautres actions de navigateur liées au toucher (en plus du glisser-déposer). Pour éviter les problèmes avec eux aussi :
- Les empêcher en définissant
#ball { touch-action: none }dans le CSS. - Ainsi notre code fonctionnera sur les périphériques tactiles.
- Les empêcher en définissant
Après avoir fait cela, les événements fonctionneront comme prévu. Le navigateur ne détournera pas le processus et nâémettra pas pointercancel.
Cette démo rajoute ces lignes:
Comme vous pouvez le voir, pointercancel nâapparaît plus.
Maintenant, nous pouvons ajouter le code pour effectivement déplacer le ballon, et notre glisser-déposer fonctionnera pour les souris et les périphériques tactiles.
La capture de pointeur
La capture de pointeur est une fonctionnalité particulière aux événements de pointeur.
Lâidée est très simple, mais peut sembler un peu étrange à première vue, car rien de similaire existe pour tout autre type dâévénement.
La méthode principale est:
elem.setPointerCapture(pointerId)â lie les événements dupointerIdrenseigné Ãelem. Après cet appel, tous les événements de pointeur partageant le mêmepointerIdaurontelemcomme cible (comme sâils avaient lieu surelem), peu importe lâendroit où ils ont réellement été généré dans le document.
En dâautres termes, elem.setPointerCapture(pointerId) modifie la cible de tout les événements ultérieurs du pointerId renseigné vers elem.
Le lien est supprimé:
- automatiquement quand les événements
pointerupoupointercancelse produisent, - automatiquement quand
elemest supprimé du document, - quand
elem.releasePointerCapture(pointerId)est appelé.
Maintenant à quoi ça sert ? Il est temps de voir un exemple concret.
La capture de pointeur peut être utilisé pour simplifier les interactions de type glisser-déposer.
Rappelons nous comment intégrer une barre de défilement, comme détaillé dans lâarticle Les évènements Glisser-Déposer de la souris.
Nous réalisons une barre de défilement constituée dâune règle et dâun curseur (thumb).
Nous pouvons créer un élément slider pour représenter la bande et le ârunnerâ (thumb) à lâintérieur :
<div class="slider">
<div class="thumb"></div>
</div>
Avec les styles, ça ressemble à ça :
Et voici la logique de travail, telle quâelle a été décrite, après avoir remplacé les événements de souris par des événements de pointeur similaires :
- Lâutilisateur appuie sur le curseur
thumbâpointerdownse déclenche. - Ensuite, il déplace le pointeur â
pointermovese déclenche, et nous déplaçons lethumble long de la règle.- â¦Lorsque le pointeur se déplace, il peut quitter le
thumbde la barre de défilement: allez au-dessus ou en-dessous de lui. Lethumbdoit se déplacer uniquement horizontalement, en restant aligné avec le pointeur.
- â¦Lorsque le pointeur se déplace, il peut quitter le
Dans la solution basée sur les événements de la souris, pour suivre tous les mouvements du pointeur, y compris lorsquâil passe au-dessus/au-dessous du thumb, nous avons dû affecter le gestionnaire dâévénements mousemove sur lâensemble du document.
Cette solution semble un peu âsaleâ. Un des problèmes est que les mouvements de pointeur autour du document peuvent provoquer des effets secondaires, déclencher dâautres gestionnaires dâévénements (comme mouseover), totalement indépendants de la barre de défilement.
Câest là où setPointerCapture entre en jeu.
- Nous pouvons appeler
thumb.setPointerCapture(event.pointerId)dans le gestionnaire depointerdown, - Ainsi, les événements de pointeur ultérieurs prendront
thumbpour cible jusquâÃpointerup/cancel. - Quand
pointerupse déclenche (déplacement achevé), le lien est automatiquement supprimé, nous nâavons pas besoin de nous en préoccupé.
Ainsi, même si lâutilisateur déplace le pointeur sur lâensemble du document, les gestionnaires dâévénement seront appelés sur thumb. De plus, les propriétés de coordonnées des objets événement, telles que clientX/clientY, restent toujours valide â la capture affecte uniquement target/currentTarget.
Voici le code de base:
thumb.onpointerdown = function(event) {
// modifie la cible de tout les événements de pointeur (jusqu'à pointerup) sur thumb
thumb.setPointerCapture(event.pointerId);
// commencer à suivre les mouvements du pointeur
thumb.onpointermove = function(event) {
// déplacement du curseur: guette les événements sur thumb, comme tous les événements de pointeur le prennent pour cible
let newLeft = event.clientX - slider.getBoundingClientRect().left;
thumb.style.left = newLeft + 'px';
};
// sur le pointeur vers le haut terminer le suivi des mouvements du pointeur
thumb.onpointerup = function(event) {
thumb.onpointermove = null;
thumb.onpointerup = null;
// ...traiter également le "drag end" si nécessaire
};
};
// remarque: pas besoin d'appeler thumb.releasePointerCapture,
// qui se produit automatiquement sur pointerup
La démo complète:
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.
Finalement, la capture de pointeur nous confère deux avantages:
- Le code devient plus propre comme nous nâavons plus besoin dâajouter/enlever des gestionnaires sur lâensemble du
document. Le lien est libéré automatiquement. - Si il existe des gestionnaires de
pointermovedans le document, ils ne seront pas accidentellement activés par le pointeur lorsque lâutilisateur déplace le curseur.
Les événements de capture de pointeur
Il y a encore une chose à mentionner ici, par souci dâexhaustivité.
Il existe deux événements de pointeur associés :
gotpointercapturese déclenche quand un élément utilisesetPointerCapturepour activer la capture.lostpointercapturese déclenche quand la capture est libérée: soit de manière explicite avec un appel ÃreleasePointerCapture, ou automatiquement surpointerup/pointercancel.
Résumé
Les événements de pointeur autorisent la gestion simultanée dâévénements de souris, tactile et de stylet, avec un seul morceau de code.
Les événements de pointeur héritent des événements de souris. Nous pouvons remplacer mouse par pointer dans les noms dâévénement et sâattendre à ce que le code continue de fonctionner pour les souris, avec une meilleure prise en charge dâautres types dâappareil.
Pour les interactions de glisser-déposer et tactiles complexes que le navigateur pourrait choisir de détourner et de gérer lui-même â pensez à annuler lâaction par défaut sur les événements et à définir touch-action: none dans le CSS pour les éléments impliqués.
Les capacités additionnelles des événements de pointeur sont:
- La prise en charge du multi-touch en utilisant
pointerIdetisPrimary. - Les propriétés spécifiques à un périphérique, tel que
pressure,width/heightet autres. - La capture de pointeur: nous pouvons modifier la cible de tout les événements de pointeur vers un élément spécifique jusquâÃ
pointerup/pointercancel.
à ce jour, les événements de pointeur sont pris en charge dans tous les principaux navigateurs, ainsi nous pouvons y passer sans problème, plus particulièrement si IE10 et Safari 12 ne sont pas requis. Et même avec ces navigateurs, il existe des polyfills qui permettent la prise en charge des événements de pointeur.
Commentaires
<code>, pour plusieurs lignes â enveloppez-les avec la balise<pre>, pour plus de 10 lignes - utilisez une sandbox (plnkr, jsbin, codepenâ¦)