Montrer la voie à suivre

Sérgio Gomes

Autrefois, il était simple de pointer du doigt sur le Web. Vous aviez une souris, vous l'avez déplacée, parfois vous avez appuyé sur des boutons, et c'est tout. Tout ce qui n'était pas une souris était émulé comme une seule souris, et les développeurs savaient exactement sur quoi compter.

Toutefois, la simplicité ne veut pas nécessairement dire bien. Au fil du temps, il est devenu de plus en plus important que tout ne soit pas (ou ne faisait semblant d'être) une souris: vous pouviez disposer de stylos sensibles à la pression et d'inclinaison pour une liberté créative incroyable, vous pouviez utiliser vos doigts, donc tout ce dont vous aviez besoin, c'était l'appareil et votre main. Et pourquoi ne pas utiliser plus d'un doigt lorsque vous êtes dessus ?

Nous utilisons des événements tactiles depuis un certain temps pour nous aider à ce sujet, mais il s'agit d'une API entièrement distincte, spécialement dédiée au toucher, ce qui vous oblige à coder deux modèles d'événements distincts si vous souhaitez prendre en charge à la fois la souris et le toucher. Chrome 55 intègre une norme plus récente qui unifie les deux modèles: les événements de pointeur.

Un modèle à événement unique

Les événements de pointeur unifient le modèle d'entrée du pointeur pour le navigateur, en regroupant les éléments tactiles, les stylos et les souris en un seul ensemble d'événements. Exemple :

document.addEventListener('pointermove',
    ev => console.log('The pointer moved.'));
foo.addEventListener('pointerover',
    ev => console.log('The pointer is now over foo.'));

Voici la liste de tous les événements disponibles, qui devrait vous sembler familier si vous connaissez les événements de souris:

pointerover Le pointeur est entré dans le cadre de délimitation de l'élément. Cela se produit immédiatement pour les appareils compatibles avec le survol ou avant un événement pointerdown pour les autres.
pointerenter Semblable à pointerover, mais ne crée pas de bulles et gère les descendants différemment. En savoir plus sur la spécification
pointerdown Le pointeur est passé à l'état de bouton actif, avec un appui sur un bouton ou un contact en cours d'établissement, en fonction de la sémantique du périphérique d'entrée.
pointermove Le pointeur a changé de position.
pointerup Le pointeur a quitté l'état du bouton actif.
pointercancel Un événement s'étant produit, il est donc peu probable que le pointeur émette d'autres événements. Cela signifie que vous devez annuler toutes les actions en cours et revenir à l'état d'entrée neutre.
pointerout Le pointeur a quitté le cadre de délimitation de l'élément ou de l'écran. Également après une pointerup, si l'appareil n'est pas compatible avec le pointage.
pointerleave Semblable à pointerout, mais ne crée pas de bulles et gère les descendants différemment. En savoir plus sur la spécification
gotpointercapture L'élément a reçu une capture de pointeur.
lostpointercapture Le pointeur en cours de capture a été libéré.

Différents types d'entrées

En règle générale, les événements de pointeur vous permettent d'écrire du code indépendamment des entrées, sans avoir à enregistrer des gestionnaires d'événements distincts pour différents périphériques d'entrée. Bien sûr, vous devrez toujours tenir compte des différences entre les types d'entrées, telles que l'application ou non du concept de pointage. Si vous souhaitez distinguer les différents types de périphériques d'entrée (par exemple, pour fournir un code ou des fonctionnalités distincts pour différentes entrées), vous pouvez le faire depuis les mêmes gestionnaires d'événements à l'aide de la propriété pointerType de l'interface PointerEvent. Par exemple, si vous codiez un panneau de navigation latéral, vous pouvez appliquer la logique suivante à votre événement pointermove:

switch(ev.pointerType) {
    case 'mouse':
    // Do nothing.
    break;
    case 'touch':
    // Allow drag gesture.
    break;
    case 'pen':
    // Also allow drag gesture.
    break;
    default:
    // Getting an empty string means the browser doesn't know
    // what device type it is. Let's assume mouse and do nothing.
    break;
}

Actions par défaut

Dans les navigateurs tactiles, certains gestes sont utilisés pour faire défiler, zoomer ou actualiser la page. Dans le cas d'événements tactiles, vous continuez à recevoir des événements pendant que ces actions par défaut sont en cours d'exécution. Par exemple, touchmove est toujours déclenché pendant que l'utilisateur fait défiler la page.

Avec les événements de pointeur, chaque fois qu'une action par défaut comme le défilement ou le zoom est déclenchée, vous recevez un événement pointercancel pour vous indiquer que le navigateur a pris le contrôle du pointeur. Exemple :

document.addEventListener('pointercancel',
    ev => console.log('Go home, the browser is in charge now.'));

Vitesse intégrée: ce modèle offre de meilleures performances par défaut que les événements tactiles, pour lesquels vous devriez utiliser des écouteurs d'événements passifs pour obtenir le même niveau de réactivité.

Vous pouvez empêcher le navigateur de prendre le contrôle avec la propriété CSS touch-action. Si vous définissez cette valeur sur none sur un élément, toutes les actions définies par le navigateur lancées à partir de cet élément seront désactivées. Toutefois, il existe un certain nombre d'autres valeurs pour un contrôle plus précis, telles que pan-x, pour permettre au navigateur de réagir aux mouvements sur l'axe X, mais pas sur l'axe Y. Chrome 55 accepte les valeurs suivantes:

auto Par défaut : le navigateur peut effectuer n'importe quelle action par défaut.
none Le navigateur n'est autorisé à effectuer aucune action par défaut.
pan-x Le navigateur n'est autorisé qu'à effectuer l'action par défaut de défilement horizontal.
pan-y Le navigateur n'est autorisé à effectuer que l'action par défaut de défilement vertical.
pan-left Le navigateur n'est autorisé qu'à effectuer l'action par défaut de défilement horizontal et uniquement à faire un panoramique vers la gauche.
pan-right Le navigateur n'est autorisé qu'à effectuer l'action par défaut de défilement horizontal et uniquement à faire un panoramique vers la droite.
pan-up Le navigateur n'est autorisé qu'à effectuer l'action par défaut de défilement vertical et uniquement à faire un panoramique vers le haut.
pan-down Le navigateur n'est autorisé qu'à effectuer l'action par défaut de défilement vertical et uniquement à faire un panoramique vers le bas.
manipulation Le navigateur n'est autorisé qu'à effectuer des actions de défilement et de zoom.

Capture du pointeur

Vous est-il déjà arrivé de passer une heure frustrante à déboguer un événement mouseup défectueux, jusqu'à ce que vous vous rendiez compte que c'était parce que l'utilisateur relâchait le bouton en dehors de votre cible de clic ? Personne ? D'accord, c'est peut-être juste moi, dans ce cas.

Pourtant, jusqu'à présent, il n'y avait pas de très bon moyen de s'attaquer à ce problème. Bien sûr, vous pouvez configurer le gestionnaire mouseup sur le document et enregistrer un état sur votre application pour suivre les choses. Cependant, ce n'est pas la solution la plus propre, en particulier si vous créez un composant Web et que vous essayez de garder tout de manière soignée et isolée.

Les événements de pointeur constituent une bien meilleure solution: vous pouvez capturer le pointeur, afin d'être sûr d'obtenir cet événement pointerup (ou tout autre de ses amis insaisissables).

const foo = document.querySelector('#foo');
foo.addEventListener('pointerdown', ev => {
    console.log('Button down, capturing!');
    // Every pointer has an ID, which you can read from the event.
    foo.setPointerCapture(ev.pointerId);
});

foo.addEventListener('pointerup', 
    ev => console.log('Button up. Every time!'));

Prise en charge des navigateurs

Au moment de la rédaction de ce document, les événements de pointeur sont compatibles avec Internet Explorer 11, Microsoft Edge, Chrome et Opera, et partiellement compatibles avec Firefox. Vous trouverez la liste à jour sur caniuse.com.

Vous pouvez utiliser le polyfill Pointer Events pour combler les écarts. Vous pouvez également vérifier facilement la compatibilité du navigateur au moment de l'exécution:

if (window.PointerEvent) {
    // Yay, we can use pointer events!
} else {
    // Back to mouse and touch events, I guess.
}

Les événements de pointeur sont un candidat idéal pour une amélioration progressive: il vous suffit de modifier vos méthodes d'initialisation pour effectuer la vérification ci-dessus, d'ajouter des gestionnaires d'événements de pointeur dans le bloc if et de déplacer vos gestionnaires d'événements tactiles et de souris dans le bloc else.

N'hésitez pas à les essayer et à nous dire ce que vous en pensez !