Zone déplacable déplacée en raison de la modification de la hauteur de la classe / de la hauteur lors de l'événement tactile

Il existe un comportement étrange sur les appareils tactiles concernant la zone cliquable d'un lien, si vous basculez la hauteur d'un élément placé ci-dessus. Si vous exécutez l'extrait suivant (par exemple, enregistrez-le localement et utilisez chrome pour imiter les événements tactiles), vous remarquerez que le hash #mylink est ajouté à l'URL dans certains cas, vous n'avez pas cliqué sur la zone de lien rouge. Et parfois, vous avez cliqué sur la zone rouge, il ne sera pas ajouté.

Par exemple

  • Si vous cliquez sur la zone gris supérieure (première 200px), la zone grise bascule la hauteur. => Ok.
  • Si vous cliquez sur la zone grise améliorée, inférieure (200px – 400px), #mylink est ajouté à l'url comme si vous aviez cliqué sur la zone de lien rouge. => Pourquoi?
  • Si vous cliquez sur la zone rouge supérieure (première 200px, zone grise minimisée), #mylink n'est pas ajouté à l'url. => Pourquoi?
  • Si vous cliquez sur la zone rouge inférieure (dernier 200px, la zone grise est améliorée), #mylink n'est pas ajouté à l'url. => Pourquoi?
 <html> <head> <style type="text/css"> .test { background: grey; height: 200px; } .test.is-visible { height: 400px; } .link { height: 600px; display: block; background: red; } </style> <script type="text/javascript"> (function() { window.addEventListener( 'touchstart' , function() { document.getElementsByClassName("test")[0].classList.toggle('is-visible'); }); })(); </script> </head> <body> <div class="test"> Click the grey area. Element enhances and minimizes itself. Now click on the lower half of the enhanced grey area (if grey = enhanced) or on the white area up to 200px below of the red area (if grey = minimized). #mylink is added to the url as if you had clicked on the red area. Why? </div> <a href="#mylink" class="link">The red area is the "normal" click area.</a> </body> </html> 

J'ai essayé cela sur iOS, Android et des événements tactiles emulés dans Chrome et Firefox. Ils se comportent tous de la même manière.

Si j'écoute le click au lieu de l' touchstart ou touchend , cela fonctionne comme prévu.

Quelle est la raison de cet écart? Pourquoi la zone de lien est-elle différente / mal placée si j'écoute des événements tactiles au lieu d'un événement de clic?

C'est ce qui se passe lorsque vous avez amélioré la zone grise et appuyez sur la moitié inférieure:

  • Lorsque vous touchez la moitié inférieure de la zone grise, votre événement touchstart est déclenché
  • Votre fonction s'exécute et bascule (supprime) la classe is-visible sur l'élément gris
  • La hauteur de l'élément gris devient plus petite
  • L'élément rouge se déplace 200px vers le haut
  • Votre touche n'a pas bougé mais les éléments de la page ont – vous touchez maintenant l'élément rouge
  • Lorsque vous relâchez votre touche, l'événement de touchend est déclenché sur l'élément rouge, qui est l'événement qui détermine si un lien a été touché ou non

La même chose se produit lorsque vous cliquez sur la zone 200px sous l'élément rouge:

  • Lorsque vous touchez la zone sous l'élément rouge, votre événement de touchstart est déclenché
  • Votre fonction s'exécute et bascule (ajoute) la classe is-visible sur l'élément gris
  • La hauteur de l'élément gris devient plus grande
  • L'élément rouge se déplace 200px vers le bas
  • Votre touche n'a pas bougé mais les éléments de la page ont – vous touchez maintenant l'élément rouge
  • Lorsque vous relâchez votre touche, l'événement de touchend est déclenché sur l'élément rouge, qui est l'événement qui détermine si un lien a été touché ou non

TL; DR

Les navigateurs exécutent un événement de clic après avoir touché des événements sur l'emplacement du touchend , peu importe si le contenu a changé en raison des fonctions assignées aux événements tactiles.


Après avoir creusé à travers la spécification Touch Events, je pense que je peux répondre à ma question:

La section 8 décrit l'interaction des événements tactiles avec les événements de la souris et cliquez sur. Il dit que:

L'agent utilisateur peut expédier à la fois les événements tactiles et (…) les événements de la souris en réponse à la même entrée utilisateur.

et

Si l'agent utilisateur interprète une séquence d'événements tactiles comme un geste de prise, il devrait expédier mousemove, mousedown, mouseup et cliquer sur les événements (dans cet ordre) à l'emplacement de l'événement touchend pour l'entrée tactile correspondante.

Si le contenu du document a changé pendant le traitement des événements tactiles, l'agent utilisateur peut envoyer les événements de la souris à une cible différente de celle des événements tactiles.

Et le plus important

L'activation d'un élément (par exemple, dans certaines implémentations, un robinet) produirait typiquement la séquence d'événements suivante (bien que cela puisse varier légèrement, selon le comportement spécifique de l'agent utilisateur):

1.) touchstart

2.) Zéro ou plus d'événements tactiles, selon le mouvement du doigt

3.) touche

4.) mousemove (pour la compatibilité avec le code spécifique à la souris hérité)

5.) mousedown

6.) mouseup

7.) cliquez sur

Pour conclure pour notre exemple ci-dessus, cela signifie que:

1.) Le touchstart ou le déclencheur est déclenché.

2.) La fonction personnalisée est traitée en premier, ce qui bascule la classe et change la hauteur / position des éléments.

3.) Ensuite, l'événement de click est exécuté sur le même point où l'événement touch s'est produit, mais couvre maintenant une cible différente.

La spécification fournit une façon d'éviter cela aussi:

Si le touchstart, le touchmove ou le touchend sont annulés, l'agent utilisateur ne doit pas envoyer d'événement sur la souris qui résulterait consécutivement de l'événement tactile empêché.

Je suppose donc que le moyen le plus simple de "corriger" ce comportement est d'empêcher l'événement avec `preventDefault () ', puis cliquez manuellement sur la cible de l'événement et basculez enfin la classe:

  window.addEventListener( 'touchstart' , function(event) { event.preventDefault(); event.target.click(); document.getElementsByClassName("test")[0].classList.toggle('is-visible'); }); 

Il y a une déclaration dans le suivi des bugs de Chromium qui prouve cela aussi.