Ordre des événements JavaScript : click, touch, focus et confrères

Ordre des événements JavaScript : click, touch, focus et confrères

Les événements JavaScript sont parfois capricieux et, dans des applications un temps soit peu complexes, on arrive vite à plusieurs écouteurs (listeners) sur un même élément, mais on rencontre rapidement des bugs dus à l'ordre de déclenchement de ceux-ci.

Je travail en ce moment sur l'intégration d'un site dans lequel j'ai mis en place plusieurs menus déroulants. Pour des raisons d'accessibilité, j'ai souhaité que ces menus s'ouvrent également lorsque l'on navigue à la tabulation, c'est à dire au focus. J'ai rencontré un problème d’ordonnancement d'événement entre click et focus/blur et c'est suite à cela que j'ai décidé d'écrire cet article pour détailler un peu tout ça.

Expérience

Pour comprendre très simplement la chose, je vais exécuter un morceau de code qui va écouter chacun des événements suivants :

  • mousedown
  • mouseup
  • focus
  • blur
  • touchstart (un nouveau point est créé sur une surface tactile)
  • touchmove (un point bouge)
  • touchend (un point disparaît)
  • click

Comme vous le voyez, je vais faire le test à l'extrême, c'est à dire en utilisant un appareil qui va déclencher les événements tactiles touchstart, touchmove et touchend. De cette façon, nous auront une vision encore plus large de la façon dont sont déclenchés ces événements. J'effectuerais les tests sur un élément focusable, ici un lien.

J'utilise jQuery histoire d'alléger le code de test et j'arrive au code suivant :

Ce code se contente d'ajouter les événements sus-cités et de lancer la fonction log qui va afficher le type de l'événement. Cette fonction log permet simplement de ne pas répéter du code pour chaque handler.

Vous pouvez le tester vous-même sur n'importe quel navigateur, voire sur un smartphone, une tablette ou bien en utilisant les options avancées de Chrome permettant d'émuler les événements de toucher.

Pour ce test, je vais me contenter d'effectuer un clic dans la zone de gauche (le lien donc).

Ainsi, j'obtiens les résultats suivants, par navigateur :

  • Chrome, Safari
    1. mousedown
    2. mouseup
    3. click
  • Chrome (emulate touch)
    1. touchstart
    2. mousedown
    3. touchend
    4. mouseup
    5. click
  • Firefox, Internet Explorer 10
    1. mousedown
    2. focus
    3. mouseup
    4. click
  • Chrome (Android)
    1. touchstart
    2. touchstop
    3. mousedown
    4. mouseup
    5. click
  • Firefox Mobile (Android)
    1. touchstart
    2. touchstop
    3. mousedown
    4. focus
    5. mouseup
    6. click

Analyse

Ces résultats sont bruts de décoffrage. Analysons un peu maintenant le déclenchement des événements.

Pour commencer, touchmove n'est jamais déclenché dans le cas d'un clic, c'est déjà un bon point, cela évite un événement parasite. On pourra noter également que click est toujours le dernier événement déclenché.

Ensuite, on peut distinguer deux familles : webkit et les autres. En effet, nous avons d'un côté Chrome et Safari qui ne font pas de chichis, simplement les événements de souris. On remarquera que l'émulation des touch events n'est pas tout à fait correcte en comparaison avec les versions mobiles qui enchaînent touchstart et touchend avant de déclencher les événements de souris.

Problème rencontré

Firefox et IE10 (et peut-être d'autres versions) ont la particularité de donner le focus à l'élément cliqué, avant la notification du clic. Ce comportement est très certainement présent pour des raisons d'accessibilité, mais dans mon cas c'est ici que j'ai eu un soucis. En effet, l'événement focus (et blur) est également celui déclenché lorsque l'on se déplace sur l'élément via le clavier à la touche tabulation. Or, j'avais besoin de différencier les deux cas d'usage. C'est là que nous allons devoir ruser avec les outils à notre disposition.

Dans le cas de mon problème, les événements liés au touch ne sont pas incriminés, mais j'ai souhaité les faire apparaître dans l'expérience ci-dessus pour être le plus exhaustif possible.

Solutions possibles

Pour pouvoir gérer d'une part le focus, et d'une autre le click, une des solutions consiste tout simplement à faire attendre le handler du focus quelques millisecondes, le temps que le click ait été déclenché. Ainsi, il est possible de le changer la valeur d'un booléen entre temps, par exemple et si ce booléen est faux alors il s'agit réellement d'un événement focus au clavier. C'est ce genre de solution que l'on voit un peu partout sur les forums.

Code exemple :



Une autre possibilité est d'utiliser le fait que le focus soit déclenché entre mousedown et mouseup, suite aux observations de l'expérience ci-dessus, avec la même astuce du booléen :

Cette seconde solution a l'avantage de ne pas présenter de latence, malgré deux écouteurs (listeners) en plus. C'est donc cette seconde solution que je conseillerais d'utiliser par rapport à la première.

Conclusion

Ce style d'astuce m'a permis de contourner le soucis du au déclenchement du focus sur Firefox et IE lors du clic. J'ai ainsi pu gérer vraiment séparément le focus clavier du focus souris (qui du coup est géré par le clic).

N'hésitez à pas à ajouter vos propres expériences / constations / résultats en commentaire de cet article.


C'est le premier article vraiment technique de ce blog, après un long vide, mais d'autres suivront sous peu. J'espère qu'il sera utile au plus grand nombre, bien que ce genre de situation tordue soit assez rare.

Votre gravatar
Gravatar de Smithc429 Smithc429
I really like your writing style, excellent info, thank you for putting up eakbgaebdedkbeef
Répondre
Gravatar de domi domi
Bizarre ! f f i, sans les espaces, dans les commentaires, ça donne un carré !
par contre, il fallait comprendre : « qu'il ait »
Répondre
Gravatar de domi domi
Bonjour et merci pour ces renseignements.

Non, non, ce type de situation n'est pas rare !
Lorsqu'on vérifie un formulaire et qu'on veut donner le focus à l'élément qui possède une mauvaise valeur, c'est fort utile.

par contre, il y a de bonnes chances que l'internaute qui suive ce tuto n'ait pas une grande expérience du JS et qu'il ai des difficultés à transposer le dernier exemple à un code sans Jquery
Répondre
Gravatar de Smithd609 Smithd609
Hey very nice blog!! Man .. Beautiful .. Amazing .. I'll bookmark your website and take the feeds alsoI am happy to find a lot of useful info here in the post, we need work out more techniques in this regard, thanks for sharing. . . . . . egdkebfkagbfbgee
Répondre
Gravatar de G-rem G-rem
Tu as de drôles d'occupations les lendemains de Noël ^^
Répondre