Si vous êtes un bouton, vous n’avez qu’une seule mission
(unsung.aresluna.org)- Même un bouton aussi simple que la rotation d’une photo peut changer radicalement l’expérience utilisateur selon la manière dont il gère les saisies répétées rapides
- L’iPhone mémorise les appuis multiples de rotation à 90 degrés et enchaîne la rotation suivante dès que l’animation est terminée
- Le Nothing Phone/Android confirme les taps par retour haptique et son, mais ignore les taps suivants effectués pendant l’animation de rotation
- Lorsqu’il faut faire pivoter plusieurs photos de documents, même une UI de retouche photo occasionnelle doit permettre des actions plus rapides et prévisibles
- Plutôt que de forcer l’utilisateur à attendre la fin de l’animation, un bouton devrait accepter les entrées via une mise en tampon des taps, ou en interrompant/accélérant l’animation
Comment le bouton de rotation des photos gère les saisies répétées
- Le bouton de rotation d’une photo est un contrôle standard qui, lorsqu’on l’actionne une fois, effectue généralement une rotation de 90 degrés dans le sens antihoraire, puis répète la même action à chaque nouvel appui
- L’animation de rotation montre le changement d’orientation de la photo et aide l’utilisateur à ne pas perdre ses repères
- Si l’on appuie rapidement 8 fois pour effectuer des rotations de 90 degrés, l’image devrait faire deux tours complets et revenir à son orientation initiale, comme ce que les ingénieurs appellent un « no op »
- Sur iPhone, cette attente se vérifie en pratique
- Il mémorise les taps rapides
- Une fois la rotation précédente terminée, il exécute la rotation suivante en attente
- Sur Nothing Phone/Android, le résultat est différent
- Le bouton fournit une confirmation du tap par retour haptique et sonore
- Si l’animation de rotation précédente n’est pas terminée, le nouveau tap est ignoré
Même une UI occasionnelle a besoin d’une gestion d’entrée plus robuste pour les tâches répétitives
- Le handicap situationnel est l’idée que le handicap ne concerne pas seulement certaines personnes, mais que chacun peut effectivement se retrouver en situation de handicap dans un contexte donné
- La prise de photos relève généralement d’un usage occasionnel, mais l’appareil photo d’un téléphone peut se tromper d’orientation lorsque l’on photographie vers le bas
- Si l’on prend plusieurs photos de documents en mode paysage puis qu’il faut les faire pivoter une par une, l’utilisateur peut devoir faire tourner des dizaines de photos
- Dans ce contexte, il est plus agréable et moins fastidieux qu’un bouton réagisse de façon prévisible même si l’on appuie trois fois à la vitesse souhaitée
- Sur une durée suffisamment longue ou auprès d’un grand nombre d’utilisateurs, même une interface occasionnelle rencontre des moments où elle doit être traitée plus sérieusement
- Cette « puissance utilisateur situationnelle » apparaît aussi dans des cas comme la retouche photo sur mobile ou GarageBand
- La solution n’est pas nécessairement une mise en tampon des taps
- Un tap interruptif peut aussi arrêter ou accélérer l’animation
- L’essentiel est de ne pas obliger l’utilisateur à attendre la fin de l’animation
2 commentaires
Avis sur Hacker News
Que faire si l’utilisateur clique trop de fois par erreur, ou s’il pense que son premier clic n’a pas été pris en compte ?
Je me souviens encore d’une keynote où Steve Jobs montrait à quel point l’affichage des documents PDF était devenu plus rapide dans le nouveau macOS. Les ingénieurs avaient prévu un bouton pour faire défiler le PDF à l’écran, mais Jobs a appuyé dessus une fois de trop par erreur et a dit quelque chose comme « ça va le faire défiler jusqu’au bout deux fois ? » ; et, effectivement, l’action avait été mise en file d’attente. Il a donc fallu remonter en haut et attendre la fin du deuxième défilement
Jobs s’en est sorti en disant que « même en comptant le temps de parcourir le document une deuxième fois, c’est plus rapide que les PDF dans l’ancien OS »
Le debouncing existe pour une raison. Il y a des cas où appuyer deux fois sur un bouton doit déclencher deux exécutions, et des cas où ce ne doit pas être le cas. Distinguer quelle option est préférable selon la situation n’a rien de simple
Il faut au minimum réfléchir au traitement adapté à chaque contexte. Dans une UI, certains boutons relèvent clairement d’un côté, d’autres clairement de l’autre, mais pour certains c’est ambigu ; si les deux comportements peuvent se défendre, on finit par obtenir une UI incohérente
Je ne connais pas vraiment de bonne solution
D’après l’article :
C’est ça, le problème. Le nombre de fois où l’app indique que l’entrée du bouton a été enregistrée doit être le même que le nombre d’actions réellement effectuées. Le debouncing est une bonne pratique, mais si on l’utilise, il ne faut pas donner de feedback pour les taps qui ont été filtrés
J’ai été assez dérouté par le passage « et si l’utilisateur clique trop de fois par erreur, ou pense que le premier clic n’a pas été enregistré ? », parce que ça m’a immédiatement fait penser à l’accessibilité. J’ai tout de suite imaginé une personne qui a des tremblements et qui veut appuyer une seule fois, mais finit par appuyer deux fois
Et il y a aussi des gens qui double-cliquent sur tous les boutons. Désactiver un bouton de soumission dans
onclickpour gérer ce cas est très courantJe ne suis plus le fanboy Apple/Mac/Jobs que j’ai pu être, mais les présentations Apple qui donnaient l’impression d’être menées par quelqu’un qui voulait montrer quelque chose de génial me manquent
Les présentations Apple récentes sont tellement rigides et déshumanisées, avec une impression de gens en costume aux commandes, que je n’arrive même pas à les regarder jusqu’au bout. Tout le monde a l’air de robots qui cachent toute émotion et toute humanité, de peur d’être renvoyés s’ils s’écartent du script sacré
C’est peut-être hors sujet, mais ça me rappelle à quel point même les improvisations un peu brouillonnes de Jobs pouvaient être agréables à suivre
Ce n’est clairement pas une situation où l’on peut dire « et dans ce cas alors ? ». Pour deux raisons :
Je veux bien soutenir l’argument qu’on voit ailleurs du type « et le debouncing alors ? », et l’auteur ne devrait pas simplement l’ignorer
Mais je n’aime pas non plus le mème you had one job, et j’ai envie de m’opposer à son usage irréfléchi. La plupart du temps, ce mème est faux, et c’est généralement pareil pour les boutons. Dans une interface utilisateur courante, un bouton donné remplit simultanément plusieurs des rôles suivants :
L’expression « une seule tâche » ne convient pas vraiment à un bouton. Un bouton ne doit pas seulement être cliquable : il doit faire plusieurs choses
Il y a le feedback au clic, le feedback au survol, l’état de chargement, l’état désactivé, et toutes les combinaisons de tout cela. Ça peut devenir très frustrant. Par exemple, si vous appuyez sur un bouton connecté à un service backend et qu’il n’y a aucun feedback visuel, vous ne savez pas si le bouton a bien été pressé. S’il n’y a pas non plus d’état de chargement, vous ne savez pas si quelque chose est en cours en arrière-plan, et vous finissez par appuyer plusieurs fois
L’auteur veut que, quelle que soit la vitesse, trois clics déclenchent toujours la même fonction. Il veut pouvoir faire défiler rapidement des photos et les faire pivoter chacune de manière prévisible, ce qui est légitime
L’auteur invoque l’accessibilité
Cela dit, on peut aussi imaginer l’argument exactement inverse. Par exemple, une personne ayant un trouble moteur ou la maladie de Parkinson pourrait préférer un comportement où, une fois l’intention déjà confirmée, les clics excessifs sont ignorés
Concevoir correctement ce genre de chose est délicat
iOS propose une option d’accessibilité appelée Ignore Repeats, mais comme c’est un réglage global du système, cela semble être une meilleure approche. Les personnes qui ont besoin de ce type de correction peuvent aussi en bénéficier dans des endroits comme le clavier à l’écran, sans que tout le monde doive ralentir sa saisie
Je ne pense pas que chaque widget d’UI doive aller jusque-là
Ça pourrait sans doute être géré par un réglage à l’échelle de l’appareil. Par exemple, ignorer des appuis répétés effectués en moins de 100 ms à moins de 50 px les uns des autres
Entièrement d’accord. Même sans aller jusqu’à la maladie de Parkinson, quand on voit des personnes pas extrêmement âgées mais déjà d’un certain âge utiliser un téléphone et un écran tactile, on constate souvent qu’il leur est difficile d’appuyer sur un petit bouton au bon endroit et d’avoir la sensation « j’ai bien appuyé »
Donc, du point de vue de l’accessibilité, le comportement du Nothing Phone me semble bien mieux adapté. Ces utilisateurs ne cliquent pas 6 fois en comptant pour placer une image à un emplacement précis. Et en plus, cliquer 8 fois à l’avance pour revenir exactement à la position actuelle n’a pas beaucoup de sens
Dans ce cas, le mode de pensée ressemble plutôt à : « cliquer, attendre, comparer avec la position voulue, puis cliquer à nouveau ». À l’inverse, un bouton sensible qui bufferise les entrées risque de provoquer des dépassements : on va trop loin, puis on revient trop en arrière. C’est similaire au fait de rater sans cesse l’endroit voulu en faisant défiler une liste
L’approche de l’iPhone convient davantage aux jeunes ou aux passionnés de technologie qui veulent aller vite sans attendre. C’est comme avec un clavier d’ordinateur, où la latence permet de saisir plusieurs caractères avant même que le premier apparaisse à l’écran
J’ai déjà utilisé un appareil avec un bouton physique : quand on appuyait dessus, il faisait bip et ajoutait 30 secondes au temps. Mais parfois, il faisait bip sans ajouter 30 secondes, et d’autres fois, il ajoutait 30 secondes sans faire de bruit. Il fallait donc toujours plisser les yeux devant l’afficheur sombre pour vérifier si l’action avait bien eu lieu
Je trouvais que c’était une conception particulièrement mauvaise, mais depuis, j’ai vu d’innombrables boutons purement logiciels reproduire exactement ce comportement cassé. Le changement de couleur du bouton à l’écran et l’exécution réelle de l’action demandée ne sont que vaguement liés. Pourquoi ? Comment est-ce seulement implémenté ?
On écrit du code impératif qui émet deux commandes, et chacune peut échouer indépendamment
Il existe beaucoup de façons de faire semblant d’avoir « traité » le problème
D’abord, tous les tests passeront, donc la plupart des développeurs peuvent arrêter d’y penser immédiatement
On peut aussi se dire qu’il suffit d’attraper l’exception et de la logger, mais ça ne corrige rien
On peut le faire tourner un moment en production pour voir si des problèmes apparaissent. Si un problème finit par survenir, le développeur réessaiera ; la deuxième fois, ça marchera probablement, et il pourra encore arrêter d’y penser
Il y a quelques jours, il y a aussi eu une grande discussion sur le pattern outbox. L’idée est de découper la première action en deux moitiés traitées atomiquement, et de confier la seconde au lecteur
À mon avis, si ce problème continue d’apparaître dans le monde réel, c’est parce que les développeurs sont dans un état de superposition quantique entre « ça n’arrive pas », « je l’ai corrigé » et « on ne peut pas le corriger »
Le code qui change la couleur d’un bouton fait partie du comportement interne du composant « bouton », donc il n’a pas besoin d’être réimplémenté pour chaque bouton. Mais cela signifie aussi qu’il est en partie séparé du code qui exécute l’action réelle. Si le gestionnaire
on clickcontient une vérification de dernier recours comme dans l’article original — « ne pas faire pivoter l’image si l’animation de rotation est en cours » — il n’existe souvent aucun moyen de dire au bouton d’annuler son changement de couleur. À l’inverse, le gestionnaireon clickpeut s’exécuter même si l’animation de changement de couleur ne s’est pas jouée correctementUn bouton change généralement de couleur quand on enfonce le bouton de la souris, mais l’action est exécutée quand on le relâche. Cette approche donne volontairement la possibilité de faire glisser le pointeur hors du bouton tout en maintenant l’appui pour annuler au dernier moment, et sur mobile, l’interaction peut être réinterprétée comme un défilement plutôt que comme un clic. Dans d’autres cas, cela augmente simplement les occasions où l’action est bloquée alors que la couleur a déjà changé
C’est totalement indépendant du fait que cet appui soit une entrée utile dans l’état actuel du logiciel. Un logiciel bien écrit devrait savoir qu’il ne peut pas accepter l’entrée et aurait dû désactiver le bouton ; même un logiciel raisonnablement bien fait devrait fournir un retour presque immédiat indiquant que l’action est en cours de traitement ou a été annulée
Les gens oublient souvent que l’animation n’est qu’un élément d’accompagnement, et qu’elle n’est pas une fin en soi
Les animations existent pour masquer les temps de chargement et fluidifier le passage d’un état à un autre. C’est pour cela qu’on les utilise
Ce savoir a fini par disparaître, et désormais on a du code qui doit attendre la fin d’une animation
Encore un exemple étonnant de culte du cargo
En réalité, je ne suis pas d’accord avec le terme « masquer ». Sur un écran tactile, faire apparaître instantanément quelque chose sans indice sur la raison de son apparition est clairement un antipattern
L’ancien Apple comprenait que le design ne se limitait pas à faire joli. L’Apple actuel façon verre ne se soucie que de l’apparence en démo
En voyant la première comparaison, honnêtement, je pense que le problème vient de l’exemple de l’iPhone. Chaque fois qu’on appuie sur le bouton de rotation, le bouton et le curseur sous l’image disparaissent puis réapparaissent en fondu ; c’est un comportement que j’ai vu dans plusieurs apps iOS et qui m’agace sans fin. En particulier, l’app Captures d’écran est une grosse source de frustration pour moi
Si un élément d’UX peut être manipulé avant et après une interaction, qu’il s’agisse d’une transformation ou d’un traitement, il devrait rester visible pendant toute la durée de l’opération. Quel gain UX y a-t-il à masquer ces boutons pendant la rotation sur iPhone ? Ça ne rend même pas mieux visuellement. Bien sûr, les logiciels Apple récents ont sacrifié les véritables améliorations UX sur l’autel de l’apparence
Cela dit, je suis d’accord avec l’auteur sur le fait que ce genre de tap devrait être traité indépendamment de l’animation
On pourrait faire quelque chose de similaire pour l’UX : un ensemble de contraintes de design patterns qui déclencherait un avertissement si l’on essaie de sortir un produit avec des défauts UX bien connus
Ça me rappelle l’époque où j’avais implémenté BabyButton, conçu pour être utilisé par un bébé. Au lieu du clic classique, j’utilisais l’événement
touchdown, ce qui permettait à l’enfant de voir immédiatement que quelque chose s’était produit, sans problème s’il maintenait le bouton trop longtemps ou bougeait le doigt en le gardant appuyéLa question de savoir s’il faut créer des systèmes pour cette tranche d’âge est un tout autre sujet, mais j’ai trouvé que c’était un bon défi de conception pour répondre aux besoins de l’utilisateur
Dans le monde réel, plus de 99 % des boutons font leur travail dès qu’on les enfonce. Mais dans une GUI, un bouton qui se comporterait ainsi semblerait étrange
Le véritable article qui se rapproche le plus de ce que l’auteur veut dire est celui-ci : https://aresluna.org/show-your-hands-honor/
Pendant un appel, quand on appuie sur le bouton marche/arrêt de l’écran de l’iPhone, est-ce qu’il arrive que cela mette fin à l’appel, et d’autres fois que cela éteigne seulement l’écran ?
Je n’ai toujours pas compris le schéma, mais il m’est arrivé plusieurs fois d’appuyer sur ce bouton avant de mettre l’iPhone dans ma poche et de raccrocher par erreur
Commentaires sur Lobste.rs
C’est une légère pique, mais si l’on parle de bonne UI/UX, une vidéo intégrée sans contrôles ni indication est ce qu’il y a de pire.
Beaucoup de gens désactivent la lecture automatique par défaut, et je suis resté un bon moment devant deux images presque identiques en me demandant : « alors, qu’est-ce qui change ? »
Je suis entièrement d’accord avec « ne faites jamais attendre l’utilisateur jusqu’à la fin d’une animation ».
Dans une app, la commande de l’utilisateur doit toujours passer avant le fait que ce soit joli.
Je pense que ce principe s’applique aussi aux jeux. Dans certains jeux, il y a un très court délai entre l’ouverture d’un menu et le moment où les entrées sont enregistrées.
C’est le genre de problème où, dans un jeu avec une animation de menu sophistiquée, si l’on appuie rapidement sur pause puis sur la flèche bas, le deuxième élément devrait-il être sélectionné une fois le menu affiché ?
Dans les jeux, la mise en mémoire tampon des entrées est un sujet subtil, mais pour la navigation dans les menus, elle donne rarement une mauvaise impression.
La vidéo transmettait assez mal l’information. Il fallait la regarder attentivement pour remarquer le moment où le bouton était pressé plusieurs fois de suite.
Sur le fond, il n’est même pas évident que le téléphone Android se comporte vraiment mal. Je comprends la frustration de vouloir appuyer rapidement 8 fois pour obtenir le résultat souhaité, mais cela me fait penser à la détection des doubles-clics accidentels dans les OS de bureau.
Au final, cela ressemble surtout à un écart entre ce que le fabricant considère comme utilisable et ce que l’utilisateur ressent comme pratique. À choisir, je trouve presque plus distrayant que l’iPhone affiche le bouton « Done » même pendant l’animation.
Il est difficile de dire qu’Android a complètement tort. Il m’est déjà arrivé d’exécuter une action deux fois par erreur à cause d’une UI qui répondait lentement.
La meilleure solution serait peut-être de désactiver le bouton jusqu’à la fin de l’animation.
Le « triple clic » ne fonctionne pas, et les clics supplémentaires pendant l’animation sont ignorés. Cliquer, regarder l’écran en attendant la fin de l’animation, puis recliquer et attendre encore est lent et demande beaucoup d’attention à l’utilisateur.
Une meilleure solution semble être de fournir un retour immédiat dans l’UI. On pourrait rendre immédiatement l’état du bouton indiquant que le clic a été pris en compte, ou redessiner temporairement l’écran très vite pour montrer la progression actuelle avant de lancer l’animation complète plus lente.
Je suis fortement d’accord avec tout l’article. J’ai envie de dire que c’est un cas où l’animation viole le serment d’Hippocrate.
Le premier principe de l’animation devrait être « d’abord, ne pas nuire », autrement dit « d’abord, ne pas gêner les entrées de l’utilisateur ».
Avez-vous déjà eu affaire à un encodeur rotatif qui n’arrive pas à suivre ? Le bouton de volume de mon ampli casque ne fait rien si je le tourne trop vite.
Si j’étais président pendant une journée…
Chaque fois que je vois ce genre de chose, je me demande comment mon propre toolkit GUI se comporterait. Mon toolkit traite deux clics rapides comme un double-clic, un comportement emprunté à la plateforme Windows, et cela rend parfois fou.
Le double-clic en lui-même est une convention courante et il a une valeur distincte du clic. Idéalement, les événements de clic devraient être rendus idempotents afin que deux occurrences ne posent pas de problème. Par exemple, si un clic sélectionne un élément et qu’un double-clic exécute l’action par défaut, sélectionner deux fois le même élément n’a rien de nuisible. Mais dans la réalité, ce n’est pas toujours le cas.
Le problème est que si l’on clique très vite sur un bouton, celui-ci peut ne s’exécuter qu’une seule fois, puis le reste part en événement de double-clic qui ne fait rien. On obtient donc un comportement où la moitié des clics rapides est ignorée.
À cause de ce problème, j’ai créé un booléen appelé
triggersOnMultiClick; lorsqu’il est réglé surtrue, il émet aussi des événements de déclenchement pour les clics multiples rapides comme les doubles-clics. Toutefois, pour des raisons de compatibilité ascendante, j’ai laissé la valeur par défaut àfalse. Comme il est temps de sortir une nouvelle version majeure, et que l’argument de l’article me semble valable, je compte maintenant faire passer la valeur par défaut àtrue.Je pourrais laisser une note du genre : « Ajouté le 2 juillet 2021, valeur par défaut
false. Le 5 juillet 2026 (v13.0), la valeur par défaut a été changée entrueafin que tous les clics sur les boutons déclenchent par défaut l’action. Pour retrouver l’ancien comportement, surchargez l’événement de double-clic ou définisseztriggersOnMultiClick = falseaprès l’appel au constructeur parent. »Un comportement similaire peut aussi apparaître par hasard côté implémentation des animations. Si l’objet contient une variable de position d’animation et que le clic en définit la valeur, il y a grosso modo deux options quand on clique deux fois. Soit sauter le reste de l’animation et passer directement à l’état interne suivant, soit ignorer l’événement pour préserver la fluidité visuelle, avec quelque chose comme
if(animationCounter) return;. Le développeur Android évoqué dans l’article semble avoir choisi la seconde option.Côté Apple, ils utilisent probablement une troisième approche : une file d’attente des animations en attente, dans laquelle les événements sont placés. Ce qui m’intrigue, c’est de savoir si l’état interne change au moment de la mise en file, au moment de l’achèvement, ou si l’image est réellement tournée par animation. Si l’on interrompt l’animation en plein milieu et que l’on enregistre, peut-on obtenir une image tournée de 45 degrés ? Si c’était moi, je mettrais à jour l’EXIF de façon atomique par pas de 90 degrés, et je ferais tourner l’animation de transition séparément.
Il peut y avoir plusieurs choix raisonnables entre simplicité d’implémentation, fidélité visuelle et comportement de l’UI. Personnellement, j’aime aussi l’approche où le deuxième appui ne attend pas l’animation et fait immédiatement passer au nouvel état. Je ne pense pas que j’irais jusqu’à créer une file d’attente d’animations.
C’est amusant de modifier le code à peu près à la même période. La dernière modification était aussi un 2 juillet, et celle-ci tombe par hasard le 5 juillet. J’aime consigner ce genre d’historique directement dans les commentaires de documentation. C’est agréable à relire plus tard, comme un petit souvenir, quand on revient changer quelque chose.
Apple fournit probablement une file d’attente d’animations par défaut, mais le moment où l’état change reste un problème distinct. Sur le Web, je mettrais sans doute l’état à jour de façon atomique, avec une animation purement visuelle séparée, comme une transition CSS
transform. Cela éviterait aussi les recalculs de layout, donc ce serait probablement simple et efficace à implémenter. J’aimerais tester un jour si le fait de changer les noms de classe plus vite que l’animation provoque un saut ou une mise en file d’attente.C’est peut-être l’âge, mais il m’est déjà arrivé plusieurs fois d’être agacé par ce genre de comportement après avoir tapé deux fois par erreur.
Utiliser un bouton au même endroit, ou un double-tap, comme mécanisme de confirmation est horrible. Si une confirmation est vraiment nécessaire, il ne faut pas la rendre facile à valider par accident.
Cela dit, dans cet exemple, accepter les entrées rapides successives me semble être une bonne idée. Dans d’autres cas, ce ne sera pas forcément vrai. Certains boutons physiques empêchent intentionnellement d’appuyer deux fois jusqu’à ce que quelque chose soit « terminé » ; ce n’est pas toujours une conception consciente, mais c’est parfois le bon choix.
Beaucoup de gens doivent utiliser des appareils lents ou défectueux, et en cas de problèmes de santé comme des tremblements de la main ou des doigts, limiter les entrées peut avoir des avantages.
Cet exemple me semble être un cas où les deux points de vue peuvent se défendre. Il y a des situations où devoir attendre est encore plus frustrant, et il y en a clairement d’autres où l’on aimerait qu’il y ait une limitation des entrées, que ce soit à cause d’une animation ou d’une autre condition.
Les résultats de recherche dans les navigateurs mobiles qui se mettent à jour sous mon doigt et déplacent ailleurs l’option que j’allais toucher sont une source d’agacement vraiment incessante.
J’ai fini par m’entraîner à attendre davantage, mais il est absurde qu’une interaction UI doive être sensible à des tâches asynchrones en arrière-plan dont le temps d’achèvement varie selon la qualité de la réception.