Ajouter une touche à votre site

Les écrans tactiles sont disponibles sur de plus en plus d'appareils, des téléphones aux écrans d'ordinateurs. Votre application doit répondre à son toucher de manière intuitive et esthétique.

Les écrans tactiles sont disponibles sur de plus en plus d'appareils, des téléphones aux écrans d'ordinateur. Lorsque vos utilisateurs choisissent d'interagir avec votre UI, votre application doit répondre à leur toucher de manière intuitive.

Répondre aux états des éléments

Avez-vous déjà appuyé ou cliqué sur un élément d'une page Web et demandé si le site l'avait réellement détecté ?

Il suffit de modifier la couleur d'un élément lorsque les utilisateurs touchent ou interagissent avec des parties de votre interface utilisateur pour leur donner l'assurance de base que votre site fonctionne. Cela permet non seulement de réduire la frustration, mais aussi de donner une impression de réactivité et d'agilité.

Les éléments DOM peuvent hériter de l'un des états suivants : par défaut, en surbrillance, en survol et actif. Pour modifier l'UI pour chacun de ces états, nous devons appliquer des styles aux pseudo-classes :hover, :focus et :active, comme indiqué ci-dessous:

.btn {
  background-color: #4285f4;
}

.btn:hover {
  background-color: #296cdb;
}

.btn:focus {
  background-color: #0f52c1;

  /* The outline parameter suppresses the border
  color / outline when focused */
  outline: 0;
}

.btn:active {
  background-color: #0039a8;
}

Essayer

Image illustrant les différentes couleurs pour les états des boutons

Dans la plupart des navigateurs mobiles, les états de pointage et/ou de ciblage s'appliquent à un élément une fois que l'utilisateur a appuyé dessus.

Réfléchissez attentivement aux styles que vous définissez et à leur apparence pour l'utilisateur une fois qu'il a terminé son geste.

Supprimer les styles de navigateur par défaut

Une fois que vous avez ajouté des styles pour les différents états, vous remarquerez que la plupart des navigateurs implémentent leurs propres styles en réponse à la pression de l'utilisateur. En effet, lors du lancement initial des appareils mobiles, un certain nombre de sites n'avaient pas de style pour l'état :active. Par conséquent, de nombreux navigateurs ont ajouté une couleur ou un style de surbrillance supplémentaires pour fournir des commentaires à l'utilisateur.

La plupart des navigateurs utilisent la propriété CSS outline pour afficher un anneau autour d'un élément lorsqu'il est sélectionné. Vous pouvez le supprimer avec:

.btn:focus {
    outline: 0;

    /* Add replacement focus styling here (i.e. border) */
}

Safari et Chrome ajoutent une couleur de surbrillance en cas de pression, que vous pouvez éviter avec la propriété CSS -webkit-tap-highlight-color :

/* Webkit / Chrome Specific CSS to remove tap
highlight color */
.btn {
  -webkit-tap-highlight-color: transparent;
}

Essayer

Internet Explorer sur Windows Phone présente un comportement similaire, mais il est supprimé via une balise méta :

<meta name="msapplication-tap-highlight" content="no">

Firefox doit gérer deux effets secondaires.

La pseudo-classe -moz-focus-inner, qui ajoute un contour aux éléments tactiles, peut être supprimée en définissant border: 0.

Si vous utilisez un élément <button> sur Firefox, un dégradé est appliqué, que vous pouvez supprimer en définissant background-image: none.

/* Firefox Specific CSS to remove button
differences and focus ring */
.btn {
  background-image: none;
}

.btn::-moz-focus-inner {
  border: 0;
}

Essayer

Désactivation de la sélection de l'utilisateur

Lorsque vous créez votre interface utilisateur, il peut arriver que vous souhaitiez que les utilisateurs interagissent avec vos éléments, mais que vous vouliez supprimer le comportement par défaut consistant à sélectionner du texte lors d'un appui prolongé ou en faisant glisser la souris sur votre interface utilisateur.

Vous pouvez le faire avec la propriété CSS user-select, mais sachez que cela peut extrêmement agacer les utilisateurs s'ils souhaitent sélectionner le texte dans l'élément. Veillez donc à l'utiliser avec précaution et avec parcimonie.

/* Example: Disable selecting text on a paragraph element: */
p.disable-text-selection {
  user-select: none;
}

Implémenter des gestes personnalisés

Si vous avez une idée d'interactions et de gestes personnalisés pour votre site, tenez compte de deux points :

  1. Prendre en charge tous les navigateurs
  2. Maintenir une fréquence d&#39;images élevée

Dans cet article, nous allons examiner précisément ces sujets, en couvrant les API que nous devons prendre en charge pour toucher tous les navigateurs, puis en expliquant comment utiliser ces événements efficacement.

Selon ce que vous souhaitez que votre geste fasse, vous souhaitez probablement que l'utilisateur interagisse avec un élément à la fois ou qu'il puisse interagir avec plusieurs éléments en même temps.

Dans cet article, nous allons examiner deux exemples, qui montrent comment prendre en charge tous les navigateurs et comment maintenir la fréquence d'images élevée.

Exemple de GIF montrant un appui sur un document

Le premier exemple permet à l'utilisateur d'interagir avec un seul élément. Dans ce cas, vous pouvez souhaiter que tous les événements tactiles soient attribués à cet élément, à condition que le geste ait commencé sur l'élément lui-même. Par exemple, vous pouvez toujours contrôler l'élément en déplaçant un doigt hors de celui-ci.

Cela est utile, car cela offre une grande flexibilité à l'utilisateur, mais impose une restriction sur la façon dont l'utilisateur peut interagir avec votre UI.

Exemple de GIF montrant un appui sur un élément

Toutefois, si vous prévoyez que les utilisateurs interagiront avec plusieurs éléments en même temps (à l'aide de la technologie multipoint), vous devez limiter le toucher à l'élément spécifique.

Cette approche est plus flexible pour les utilisateurs, mais elle complique la logique de manipulation de l'UI et est moins résiliente aux erreurs des utilisateurs.

Ajouter des écouteurs d'événements

Dans Chrome (version 55 et ultérieure), Internet Explorer et Edge, PointerEvents est l'approche recommandée pour implémenter des gestes personnalisés.

Dans d'autres navigateurs, TouchEvents et MouseEvents sont la bonne approche.

PointerEvents présente l'avantage de fusionner plusieurs types d'entrées, y compris les événements de souris, d'écran tactile et de stylet, en un seul ensemble de rappels. Les événements à écouter sont pointerdown, pointermove, pointerup et pointercancel.

Dans d'autres navigateurs, les équivalents sont touchstart, touchmove, touchend et touchcancel pour les événements tactiles. Si vous souhaitez implémenter le même geste pour la saisie à la souris, vous devez implémenter mousedown, mousemove et mouseup.

Si vous avez des questions sur les événements à utiliser, consultez ce tableau des événements tactiles, de souris et de pointeur.

L'utilisation de ces événements nécessite d'appeler la méthode addEventListener() sur un élément DOM, ainsi que le nom d'un événement, une fonction de rappel et une valeur booléenne. La valeur booléenne détermine si vous devez intercepter l'événement avant ou après que d'autres éléments ont eu la possibilité de l'intercepter et de l'interpréter. (true signifie que vous souhaitez que l'événement soit placé avant les autres éléments.)

Voici un exemple d'écoute du début d'une interaction.

// Check if pointer events are supported.
if (window.PointerEvent) {
  // Add Pointer Event Listener
  swipeFrontElement.addEventListener('pointerdown', this.handleGestureStart, true);
  swipeFrontElement.addEventListener('pointermove', this.handleGestureMove, true);
  swipeFrontElement.addEventListener('pointerup', this.handleGestureEnd, true);
  swipeFrontElement.addEventListener('pointercancel', this.handleGestureEnd, true);
} else {
  // Add Touch Listener
  swipeFrontElement.addEventListener('touchstart', this.handleGestureStart, true);
  swipeFrontElement.addEventListener('touchmove', this.handleGestureMove, true);
  swipeFrontElement.addEventListener('touchend', this.handleGestureEnd, true);
  swipeFrontElement.addEventListener('touchcancel', this.handleGestureEnd, true);

  // Add Mouse Listener
  swipeFrontElement.addEventListener('mousedown', this.handleGestureStart, true);
}

Essayer

Gérer les interactions avec un seul élément

Dans le court extrait de code ci-dessus, nous n'avons ajouté que l'écouteur d'événements de démarrage pour les événements de souris. En effet, les événements de souris ne se déclenchent que lorsque le curseur passe la souris sur l'élément auquel l'écouteur d'événements est ajouté.

TouchEvents suit un geste après son démarrage, quel que soit l'endroit où le toucher se produit, et PointerEvents suit les événements quel que soit l'endroit où le toucher se produit après avoir appelé setPointerCapture sur un élément DOM.

Pour les événements de déplacement et de fin de la souris, nous ajoutons les écouteurs d'événements dans la méthode de début du geste et les ajoutons au document. Cela signifie qu'il peut suivre le curseur jusqu'à la fin du geste.

Pour ce faire, procédez comme suit:

  1. Ajoutez tous les écouteurs TouchEvent et PointerEvent. Pour les MouseEvents, n'ajoutez que l'événement de début.
  2. Dans le rappel du geste de début, associez les événements de déplacement et de fin de la souris au document. De cette façon, tous les événements de la souris sont reçus, que l'événement se produise sur l'élément d'origine ou non. Pour PointerEvents, nous devons appeler setPointerCapture() sur notre élément d'origine pour recevoir tous les autres événements. Gérez ensuite le début du geste.
  3. Gérez les événements de mouvement.
  4. À l'événement de fin, supprimez du document les écouteurs de mouvement et de fin, puis mettez fin au geste.

Vous trouverez ci-dessous un extrait de notre méthode handleGestureStart(), qui ajoute les événements de déplacement et de fin au document :

// Handle the start of gestures
this.handleGestureStart = function(evt) {
  evt.preventDefault();

  if(evt.touches && evt.touches.length > 1) {
    return;
  }

  // Add the move and end listeners
  if (window.PointerEvent) {
    evt.target.setPointerCapture(evt.pointerId);
  } else {
    // Add Mouse Listeners
    document.addEventListener('mousemove', this.handleGestureMove, true);
    document.addEventListener('mouseup', this.handleGestureEnd, true);
  }

  initialTouchPos = getGesturePointFromEvent(evt);

  swipeFrontElement.style.transition = 'initial';
}.bind(this);

Essayer

Le rappel de fin que nous ajoutons est handleGestureEnd(), qui supprime les écouteurs d'événements de déplacement et de fin du document et libère la capture du pointeur une fois le geste terminé, comme suit:

// Handle end gestures
this.handleGestureEnd = function(evt) {
  evt.preventDefault();

  if (evt.touches && evt.touches.length > 0) {
    return;
  }

  rafPending = false;

  // Remove Event Listeners
  if (window.PointerEvent) {
    evt.target.releasePointerCapture(evt.pointerId);
  } else {
    // Remove Mouse Listeners
    document.removeEventListener('mousemove', this.handleGestureMove, true);
    document.removeEventListener('mouseup', this.handleGestureEnd, true);
  }

  updateSwipeRestPosition();

  initialTouchPos = null;
}.bind(this);

Essayer

En suivant ce modèle d'ajout de l'événement de déplacement au document, si l'utilisateur commence à interagir avec un élément et déplace son geste en dehors de l'élément, nous continuerons à recevoir des mouvements de souris, quel que soit l'endroit où ils se trouvent sur la page, car les événements sont reçus à partir du document.

Ce schéma montre le comportement des événements tactiles lorsque nous ajoutons les événements de déplacement et de fin au document lorsqu'un geste commence.

Illustration de la liaison d&#39;événements tactiles au document dans &quot;touchstart&quot;

Répondre efficacement aux gestes tactiles

Maintenant que les événements de début et de fin sont gérés, nous pouvons répondre aux événements tactiles.

Pour tous les événements de démarrage et de déplacement, vous pouvez facilement extraire x et y d'un événement.

L'exemple suivant vérifie si l'événement provient d'une TouchEvent en vérifiant si targetTouches existe. Si tel est le cas, il extrait clientX et clientY à partir du premier contact. Si l'événement est PointerEvent ou MouseEvent, il extrait clientX et clientY directement de l'événement lui-même.

function getGesturePointFromEvent(evt) {
    var point = {};

    if (evt.targetTouches) {
      // Prefer Touch Events
      point.x = evt.targetTouches[0].clientX;
      point.y = evt.targetTouches[0].clientY;
    } else {
      // Either Mouse event or Pointer Event
      point.x = evt.clientX;
      point.y = evt.clientY;
    }

    return point;
  }

Essayer

Un TouchEvent contient trois listes de données tactiles :

  • touches : liste de toutes les pressions actuelles sur l'écran, quel que soit l'élément DOM sur lequel elles se trouvent.
  • targetTouches : liste des pressions actuellement sur l'élément DOM auquel l'événement est lié.
  • changedTouches : liste des gestes tactiles qui ont changé, ce qui a déclenché l'événement.

Dans la plupart des cas, targetTouches vous donne tout ce dont vous avez besoin et ce dont vous avez besoin. (Pour en savoir plus sur ces listes, consultez la section Listes tactiles.)

Utiliser requestAnimationFrame

Étant donné que les rappels d'événements sont déclenchés sur le thread principal, nous souhaitons exécuter le moins de code possible dans les rappels de nos événements, en maintenant notre fréquence d'images élevée et en évitant les à-coups.

En utilisant requestAnimationFrame(), nous avons la possibilité de mettre à jour l'UI juste avant que le navigateur ne veuille dessiner un frame, ce qui nous aidera à déplacer certaines tâches de nos rappels d'événements.

Si vous ne connaissez pas requestAnimationFrame(), cliquez ici pour en savoir plus.

Une implémentation typique consiste à enregistrer les coordonnées x et y des événements de début et de déplacement, puis à demander un frame d'animation dans le rappel d'événement de déplacement.

Dans notre démonstration, nous stockons la position tactile initiale dans handleGestureStart() (recherchez initialTouchPos):

// Handle the start of gestures
this.handleGestureStart = function(evt) {
  evt.preventDefault();

  if (evt.touches && evt.touches.length > 1) {
    return;
  }

  // Add the move and end listeners
  if (window.PointerEvent) {
    evt.target.setPointerCapture(evt.pointerId);
  } else {
    // Add Mouse Listeners
    document.addEventListener('mousemove', this.handleGestureMove, true);
    document.addEventListener('mouseup', this.handleGestureEnd, true);
  }

  initialTouchPos = getGesturePointFromEvent(evt);

  swipeFrontElement.style.transition = 'initial';
}.bind(this);

La méthode handleGestureMove() stocke la position de son événement avant de demander une image d'animation si nécessaire, en transmettant la fonction onAnimFrame() en tant que rappel:

this.handleGestureMove = function (evt) {
  evt.preventDefault();

  if (!initialTouchPos) {
    return;
  }

  lastTouchPos = getGesturePointFromEvent(evt);

  if (rafPending) {
    return;
  }

  rafPending = true;

  window.requestAnimFrame(onAnimFrame);
}.bind(this);

La valeur onAnimFrame est une fonction qui, lorsqu'elle est appelée, modifie notre UI pour la déplacer. En transmettant cette fonction à requestAnimationFrame(), nous demandons au navigateur de l'appeler juste avant qu'il ne mette à jour la page (c'est-à-dire qu'il peigne les modifications apportées à la page).

Dans le rappel handleGestureMove(), nous vérifions d'abord si rafPending est faux, ce qui indique si onAnimFrame() a été appelé par requestAnimationFrame() depuis le dernier événement de déplacement. Cela signifie qu'il n'y a qu'un seul requestAnimationFrame() en attente d'exécution à la fois.

Lorsque notre rappel onAnimFrame() est exécuté, nous définissons la transformation sur tous les éléments à déplacer avant de mettre à jour rafPending sur false, ce qui permet à l'événement tactile suivant de demander une nouvelle image d'animation.

function onAnimFrame() {
  if (!rafPending) {
    return;
  }

  var differenceInX = initialTouchPos.x - lastTouchPos.x;
  var newXTransform = (currentXPosition - differenceInX)+'px';
  var transformStyle = 'translateX('+newXTransform+')';

  swipeFrontElement.style.webkitTransform = transformStyle;
  swipeFrontElement.style.MozTransform = transformStyle;
  swipeFrontElement.style.msTransform = transformStyle;
  swipeFrontElement.style.transform = transformStyle;

  rafPending = false;
}

Contrôler les gestes à l'aide d'actions tactiles

La propriété CSS touch-action vous permet de contrôler le comportement tactile par défaut d'un élément. Dans nos exemples, nous utilisons touch-action: none pour empêcher le navigateur d'effectuer quoi que ce soit avec le toucher de l'utilisateur, ce qui nous permet d'intercepter tous les événements tactiles.

/* Pass all touches to javascript: */
button.custom-touch-logic {
  touch-action: none;
}

L'utilisation de touch-action: none est une option plutôt nucléaire, car elle empêche tous les comportements par défaut du navigateur. Dans de nombreux cas, l'une des options ci-dessous constitue une meilleure solution.

touch-action vous permet de désactiver les gestes implémentés par un navigateur. Par exemple, dans Internet Explorer 10+, vous pouvez appuyer deux fois pour zoomer. En définissant touch-action sur manipulation, vous empêchez le comportement par défaut du double appui.

Vous pouvez ainsi implémenter vous-même un geste de double-appui.

Vous trouverez ci-dessous une liste des valeurs touch-action couramment utilisées :

Paramètres d'action tactile
touch-action: none Aucune interaction tactile ne sera gérée par le navigateur.
touch-action: pinch-zoom Désactive toutes les interactions du navigateur, telles que "touch-action: none", à l'exception de "Pincer-zoom", qui est toujours gérée par le navigateur.
touch-action: pan-y pinch-zoom Gérez les défilements horizontaux en JavaScript sans désactiver le défilement vertical ni le zoom par pincement (par exemple, les carrousels d'images).
touch-action: manipulation Désactive le geste de double-clic, ce qui évite tout retard de clic par le navigateur. La navigation et le zoom par pincement sont conservés dans le navigateur.

Prise en charge des anciennes versions d'IE

Si vous souhaitez prendre en charge IE10, vous devez gérer les versions de PointerEvents avec un préfixe du fournisseur.

Pour vérifier la prise en charge de PointerEvents, vous recherchez généralement window.PointerEvent, mais dans IE10, vous recherchez window.navigator.msPointerEnabled.

Les noms d'événements avec des préfixes de fournisseurs sont les suivants : 'MSPointerDown', 'MSPointerUp' et 'MSPointerMove'.

L'exemple ci-dessous vous montre comment vérifier la prise en charge et changer les noms des événements.

var pointerDownName = 'pointerdown';
var pointerUpName = 'pointerup';
var pointerMoveName = 'pointermove';

if (window.navigator.msPointerEnabled) {
  pointerDownName = 'MSPointerDown';
  pointerUpName = 'MSPointerUp';
  pointerMoveName = 'MSPointerMove';
}

// Simple way to check if some form of pointerevents is enabled or not
window.PointerEventsSupport = false;
if (window.PointerEvent || window.navigator.msPointerEnabled) {
  window.PointerEventsSupport = true;
}

Pour en savoir plus, consultez cet article de Microsoft sur les mises à jour.

Référence

Pseudo-classes pour les états tactiles

Classe Exemple Description
:hover
Bouton en état enfoncé
Entrée saisie lorsqu'un curseur est placé sur un élément. Les modifications apportées à l'UI en cas de survol sont utiles pour encourager les utilisateurs à interagir avec les éléments.
:mise au point
Bouton avec état de sélection
Saisie lorsque l'utilisateur accède à des éléments d'une page. L'état de focus permet à l'utilisateur de savoir avec quel élément il interagit actuellement. Il permet également aux utilisateurs de naviguer facilement dans votre UI à l'aide d'un clavier.
:active
Bouton pressé
Saisie lorsqu'un élément est sélectionné, par exemple lorsqu'un utilisateur clique ou appuie sur un élément.

Pour obtenir la référence définitive sur les événements tactiles, consultez Événements tactiles du W3C.

Événements tactiles, de souris et de pointeur

Ces événements sont les éléments de base pour ajouter de nouveaux gestes à votre application :

Événements tactiles, de souris et de pointeur
touchstart, mousedown, pointerdown Cette méthode est appelée lorsqu'un doigt touche un élément pour la première fois ou lorsque l'utilisateur clique sur la souris.
touchmove, mousemove, pointermove Cette méthode est appelée lorsque l'utilisateur déplace son doigt sur l'écran ou le fait glisser avec la souris.
touchend, mouseup, pointerup Cette méthode est appelée lorsque l'utilisateur retire son doigt de l'écran ou relâche la souris.
touchcancel pointercancel Cette méthode est appelée lorsque le navigateur annule les gestes tactiles. Par exemple, un utilisateur appuie sur une application Web, puis change d'onglet.

Listes tactiles

Chaque événement tactile comprend trois attributs de liste :

Attributs d'événement tactile
touches Liste de toutes les pressions actuelles sur l'écran, quels que soient les éléments touchés.
targetTouches Liste des pressions qui ont commencé sur l'élément qui est la cible de l'événement en cours. Par exemple, si vous liez à un <button>, vous ne toucherez actuellement que ce bouton. Si vous vous liez au document, vous obtiendrez toutes les touches actuellement sur le document.
changedTouches Liste des éléments tactiles qui ont été modifiés, entraînant le déclenchement de l'événement :
  • Pour l'événement touchstart : liste des points de contact qui viennent d'être actifs avec l'événement en cours.
  • Pour l'événement touchmove, liste des points de contact qui ont bougé depuis le dernier événement.
  • Pour les événements touchend et touchcancel, liste des points de contact qui viennent d'être supprimés de la surface.

Activer la compatibilité avec l'état actif sur iOS

Malheureusement, Safari sur iOS n'applique pas l'état actif par défaut. Pour qu'il fonctionne, vous devez ajouter un écouteur d'événement touchstart au corps du document ou à chaque élément.

Vous devez le faire derrière un test de l'agent utilisateur afin qu'il ne s'exécute que sur les appareils iOS.

L'ajout d'un début de contact au corps présente l'avantage de s'appliquer à tous les éléments du DOM. Toutefois, cela peut entraîner des problèmes de performances lors du défilement de la page.

window.onload = function() {
  if (/iP(hone|ad)/.test(window.navigator.userAgent)) {
    document.body.addEventListener('touchstart', function() {}, false);
  }
};

L'alternative consiste à ajouter les écouteurs de début de pression à tous les éléments interactifs de la page, ce qui atténue certains des problèmes de performances.

window.onload = function() {
  if (/iP(hone|ad)/.test(window.navigator.userAgent)) {
    var elements = document.querySelectorAll('button');
    var emptyFunction = function() {};

    for (var i = 0; i < elements.length; i++) {
        elements[i].addEventListener('touchstart', emptyFunction, false);
    }
  }
};