Gestes pincer et zoomer
=======================

Ajouter la gestion des *gestes* à une application peut améliorer de manière significative l'expérience utilisateur. Il existe de nombreux types de gestes, du simple geste *swipe* (balayage de l'écran) aux gestes plus complexes avec plusieurs doigts comme le *twist* (rotation), où les points de contact (dits *pointeurs*) bougent dans des directions différentes.

Cet exemple montre comment détecter les gestes de *pinch/zoom* (pincer/zoomer), en utilisant les `événements de pointeur` pour détecter si l'utilisateur bouge deux pointeurs plus proches ou plus loin l'un de l'autre.

**Note :** Une version *en direct* de cette application est disponible sur Github. Le code source est également disponible sur Github; les pull requests et bug reports sont les bienvenus.

Exemple
-------

Dans cet exemple, on utilise les `événement de pointeur` pour détecter simultanément plusieurs appareils de pointage quel qu'en soit le type, comme les doigts, la souris, et le stylet. Le geste de pincer (zoomer), qui consiste à déplacer deux pointeurs plus près l'un vers l'autre, change la couleur d'arrière-plan de l'élément cible en `lightblue`. Le geste d'étirer (dézoomer), qui consiste à déplacer deux pointeur plus loin l'un de l'autre, change la couleur d'arrière-plan de l'élément cible en `pink`.

### Définir la cible du toucher

L'application utilise un `<div>` pour définir la zone cible du pointeur.


```html
<style>
 div {
 margin: 0em;
 padding: 2em;
 }
 #target {
 background: white;
 border: 1px solid black;
 }
</style>
```

### État global

Prendre en charge un mouvement à deux pointeurs nécessite de conserver un état des événements du pointeur durant les différentes phases de l'événement. Cette application utilise deux variables globales pour mettre en cache l'état de l'événement.


```js
// Variables globales pour mettre en cache l'état de l'événement
var evCache = new Array();
var prevDiff = -1;
```

### Enregistrer les gestionnaires d'événement

Les gestionnaires d'événement sont enregistrés pour les événements de pointeur suivants: `pointerdown` (en-US), `pointermove` (en-US) et `pointerup` (en-US). Le gestionnaire pour `pointerup` (en-US) est utilisé pour les événements `pointercancel` (en-US), `pointerout` (en-US) et `pointerleave` (en-US) puisque ces quatre événements ont la même sémantique dans cette application.


```js
function init() {
  // Ajoute les gestionnaires d'événements pour la cible du pointeur
  var el = document.getElementById("target");
  el.onpointerdown = pointerdown_handler;
  el.onpointermove = pointermove_handler;

  // Même chose pour les événements pointer{up,cancel,out,leave} puisque
  // la sémantique pour ces événements - dans cette appli - est identique.
  el.onpointerup = pointerup_handler;
  el.onpointercancel = pointerup_handler;
  el.onpointerout = pointerup_handler;
  el.onpointerleave = pointerup_handler;
}
```

### Pointer down

L'événement `pointerdown` (en-US) est déclenché quand un pointeur (souris, stylo/stylet ou point de contact sur un écran tactile) entre en contact avec la *surface de contact*. Dans cette application, l'état de l'événement doit être mis en cache dans le cas où il fait partie d'un geste à deux pointeurs pour pincer/zoomer.


```js
function pointerdown\_handler(ev) {
  // L'événement pointerdown signale le début d'une interraction de toucher.
  // L'événement est mis en cache pour prendre en charge les gestes à 2 doigts
  evCache.push(ev);
  log("pointerDown", ev);
}
```

### Pointer move

Le gestionnaire d'événement `pointermove` (en-US) détecte si un utilisateur est en train d'effectuer le geste de pincer/zoomer. Si deux pointeurs sont utilisés, et que la distance entre les pointeurs augmente (ce qui signifie qu'on étire ou dézoome), la couleur d'arrière-plan est changée en `pink`, et si la distance entre les pointeurs diminue (ce qui signifie qu'on pince ou dézoome), la couleur d'arrière-plan est changée en `lightblue`. Dans une application plus sophistiquée, le pincement ou l'étirement pourrait être utilisé pour appliquer une sémantique spécifique à l'application.

Quand cet événement est traité, la bordure de la cible est définie à `dashed` pour fournir une indication visuelle claire que l'élément a reçu un événement de déplacement.


```js
function pointermove\_handler(ev) {
  // Cette fonction implémente la détection du mouvement horizontal pincer/zoomer.
  //
  // Si la distance entre les deux pointeurs augmente (zoomer),
  // l'arrière-plan de l'élément cible est changé en "pink" et si la
  // distance diminue (dezoomer), la couleur est changée en "lightblue".
  //
  // Cette fonctionne définie la bordure de l'élément cible à "dashed" pour indiquer
  // visuellement que la cible du pointeur a reçu un événement de déplacement.
  log("pointerMove", ev);
  ev.target.style.border = "dashed";

  // Trouve le pointeur en cours dans le cache et le met à jour avec cet événement
  for (var i = 0; i < evCache.length; i++) {
    if (ev.pointerId == evCache[i].pointerId) {
      evCache[i] = ev;
      break;
    }
  }

  // Si deux pointeurs sont utilisés, vérifie le geste de pincement
  if (evCache.length == 2) {
    // Calcule la distance entre les deux pointeurs
    var curDiff = Math.abs(evCache[0].clientX - evCache[1].clientX);

    if (prevDiff > 0) {
      if (curDiff > prevDiff) {
        // La distance entre les deux pointeurs a augmenté
        log("Pinch moving OUT -> Zoom in", ev);
        ev.target.style.background = "pink";
      }
      if (curDiff < prevDiff) {
        // La distance entre les deux pointeurs a diminué
        log("Pinch moving IN -> Zoom out", ev);
        ev.target.style.background = "lightblue";
      }
    }

    // Met en cache la distance pour les événements suivants
    prevDiff = curDiff;
  }
}
```

### Pointer up

L'événement `pointerup` (en-US) est déclenché quand le pointeur est levé de la *surface de contact*. Quand cela arrive, l'événement est retiré du cache et la couleur d'arrière-plan et bordure de la cible sont rétablies à leur valeur d'origine.

Dans cette application, ce gestionnaire est également utilisé pour les événements `pointercancel` (en-US), `pointerleave` (en-US) et `pointerout` (en-US).


```js
function pointerup\_handler(ev) {
  log(ev.type, ev);
  // Retire ce pointeur du cache et rétablit l'arrière-plan et
  // et bordure de la cible
  remove\_event(ev);
  ev.target.style.background = "white";
  ev.target.style.border = "1px solid black";

  // Si le nombre de pointeurs restant est inférieur à deux, remet à zéro la différence
  if (evCache.length < 2) prevDiff = -1;
}
```

### Application UI

Cette application utilise un élément `<div>` comme zone de toucher et fournit des boutons pour activer et nettoyer les logs.

**Note :** Pour empêcher que le comportement par défaut du navigateur au toucher surcharge le gestionnaire de l'application, la propriété `touch-action` est appliquée à l'élément `<body>`.


```html
<body onload="init();" style="touch-action:none">
  <div id="target">
    Touchez l'écran avec deux pointeurs, puis pincez ou étirez.<br />
    La couleur d'arrière-plan changera en rose au pincement (Zoomer) ou en bleu
    clair à l'étirement (Dézoomer).
  </div>
  <!-- UI pour log/debug -->
  <button id="log" onclick="enableLog(event);">
    Démarrer/Stopper les logs
  </button>
  <button id="clearlog" onclick="clearLog(event);">Nettoyer les logs</button>
  <p></p>
  <output></output>
</body>
```

### Fonctions diverses

Ces fonctions prennent en charge l'application mais ne sont pas directement impliquées dans le flux des événements.

#### Gestion du Cache

Cette fonction aide à gérer le cache global des événements, `evCache`.


```js
function remove\_event(ev) {
  // Supprime l'événement du cache
  for (var i = 0; i < evCache.length; i++) {
    if (evCache[i].pointerId == ev.pointerId) {
      evCache.splice(i, 1);
      break;
    }
  }
}
```


#### Log des événements

Ces fonctions sont utilisées pour afficher l'activité du pointeur dans la fenêtre de l'application (pour aider à debugger et à apprendre le flux des événements).


```js
// Flag log
var logEvents = false;

// Fonctions Log/debug
function enableLog(ev) {
  logEvents = logEvents ? false : true;
}

function log(prefix, ev) {
  if (!logEvents) return;
  var o = document.getElementsByTagName('output')[0];
  var s = prefix + ": pointerID = " + ev.pointerId +
                " ; pointerType = " + ev.pointerType +
                " ; isPrimary = " + ev.isPrimary;
  o.innerHTML += s + "
";
}

function clearLog(event) {
 var o = document.getElementsByTagName('output')[0];
 o.innerHTML = "";
}
```

Voir aussi
----------

* Pointer Events now in Firefox Nightly; Mozilla Hacks; by Matt Brubeck and Jason Weathersby; 2015-Aug-04
* jQuery Pointer Events Polyfill
* Gestures; Google Design Patterns