2 points par GN⁺ 2024-12-06 | 1 commentaires | Partager sur WhatsApp
  • @stanko/dual-range-input crée un slider à double plage en combinant deux inputs range HTML natifs, tout en exploitant tels quels les interactions et l’accessibilité par défaut du navigateur
  • L’implémentation repose principalement sur environ 50 lignes de JavaScript pour synchroniser les valeurs d’entrée, ainsi que sur du CSS, ce qui la rend plus simple que les approches classiques qui recréent elles-mêmes le glisser-déposer et l’accessibilité
  • Les deux inputs voient leurs attributs min, max et leur largeur mis à jour en fonction du point médian des valeurs sélectionnées ; lorsqu’on clique sur la piste, le thumb le plus proche se déplace vers cette valeur
  • Comme la piste d’un input range est plus courte que la largeur réelle de l’input, la largeur du thumb est ajoutée au calcul et le padding du wrapper absorbe la largeur excédentaire
  • Le thème peut être ajusté avec des variables CSS et un gradient, ce qui permet de surcharger la couleur de la plage sélectionnée, la taille du thumb, la couleur de la piste, etc.

Un slider double créé avec des inputs range natifs

  • @stanko/dual-range-input implémente une saisie de plage double en plaçant côte à côte deux inputs range HTML
  • Ici, « natif » ne signifie pas qu’il s’agit d’un contrôle de navigateur unique, mais que l’utilisation de deux inputs range permet de préserver les interactions par défaut du navigateur et les fonctionnalités d’accessibilité
  • L’implémentation se compose d’environ 50 lignes de JavaScript pour synchroniser les inputs, ainsi que de CSS pour la finition visuelle
  • Le point de départ était le besoin d’une UI permettant d’ajuster les valeurs minimale et maximale de paramètres dans un outil de dessin génératif
  • Il y avait deux exigences
    • utiliser des inputs range HTML natifs
    • lorsqu’on clique sur la piste, déplacer vers cette valeur celui des deux thumbs qui est le plus proche du point cliqué

Synchronisation des valeurs et gestion des clics

  • Lorsqu’un des deux inputs change, la bibliothèque calcule le point médian entre les deux valeurs sélectionnées
    • elle définit les attributs min et max de chaque input en fonction de ce point médian
    • elle met aussi à jour la largeur des deux inputs par rapport à ce point médian
  • Comme les inputs sont redimensionnés de façon à se rejoindre au point médian, lorsqu’on clique entre les deux thumbs, c’est le thumb le plus proche de la position du clic qui se déplace
    • si le nombre de steps entre les deux thumbs est impair, le dernier input utilisé est prioritaire

Calculs CSS et stylisation

  • La piste d’un input range est plus courte que la largeur totale de l’input, et le navigateur laisse de l’espace de chaque côté pour éviter que le thumb ne dépasse vers l’extérieur
    • si l’on divise simplement la largeur de l’input en 25 % et 75 %, par exemple, le ratio réel des pistes ne correspond pas
    • l’implémentation ajoute --dri-thumb-width à la largeur de chaque input, et ajoute du padding au wrapper pour accueillir la largeur excédentaire
  • La stylisation utilise les capacités CSS de style des inputs range du navigateur
    • elle personnalise la piste et le thumb, et évite l’apparition d’un border radius dans la partie centrale où les deux pistes se rejoignent
  • Les thèmes peuvent être créés en surchargeant des variables CSS
    • les principales variables incluent --dri-height, --dri-thumb-width, --dri-thumb-color, --dri-track-color, --dri-track-filled-color, --dri-track-border-radius, etc.
  • La plage sélectionnée est colorée avec un gradient CSS
    • la position du gradient est définie par la variable --dri-gradient-position
    • lorsque le code met à jour la largeur des inputs, il met aussi à jour cette variable
  • Cette implémentation adopte une approche qui consiste à exploiter autant que possible les éléments natifs plutôt que de recréer le glisser-déposer et l’accessibilité dans une bibliothèque custom

1 commentaires

 
GN⁺ 2024-12-06
Avis de Hacker News
  • Je pense que ce genre de slider devrait toujours permettre de faire glisser la zone centrale. Autrement dit, si l’on attrape le milieu, les deux poignées devraient se déplacer en même temps.
    À titre de référence, Unity le gère ainsi : https://docs.unity3d.com/StaticFiles/ScriptRefImages/EditorG...

    • À l’inverse, je n’aurais jamais pensé à cliquer sur la zone centrale. Dans ma tête, ce n’était pas un widget entièrement nouveau, mais un slider modifié.
      « Toujours » est peut-être un peu excessif.
  • Je suis l’auteur. Content de voir ça sur HN.
    J’ai vu que des issues et des rapports de bugs commencent à arriver ici ; il est tard chez moi, donc je regarderai demain.
    Je l’ai créé parce que j’en avais besoin pour du travail d’images génératives, et je préfère faire glisser un slider pour voir comment l’image change plutôt que saisir des nombres.
    J’ai essayé de le garder aussi minimaliste que possible.

    • J’ai vraiment aimé cet article. Avec mon café du matin, j’ai pris plaisir à lire les explications sur le calcul et le mode de débogage, et j’ai trouvé que c’était une façon intelligente d’exploiter des composants natifs.
      En ce moment j’utilise SwiftUI, où créer ses propres composants est presque trop facile. Mais comme l’auteur l’explique, ce n’est pas toujours le meilleur choix, et les composants natifs sont souvent déjà assez complets.
      J’ai aussi vérifié l’article dans un navigateur un peu particulier, Firefox sur iOS, et tout fonctionnait bien. Très beau travail.
  • En l’essayant moi-même, je suis tout de suite tombé sur un bug. Après avoir fait glisser le slider sur 100 - 100, impossible de le mettre à 99 - 99.
    On ne peut pas y arriver depuis 99 - 100, ni depuis 98 - 99. Le seul moyen d’obtenir 99 - 99 est de faire descendre la borne basse sous 98.
    Depuis 98 - 99, si on descend à 97 - 99, alors on peut ensuite aller à 99 - 99.
    Ce bug peut se produire n’importe où ; 99 - 99 est simplement le cas le plus facile à reproduire parce qu’il est proche de l’extrémité du slider. En général, depuis un état X - X, si l’on déplace une extrémité d’un cran, on ne peut pas ramener l’autre extrémité dans l’écart ainsi créé. Pour refermer l’écart, il faut d’abord éloigner davantage l’autre extrémité.

    • Bien vu ! En fait j’avais pensé à ce cas, et ça fonctionne dans Chrome et Firefox, mais Safari semble ne pas déclencher l’événement focus au mousedown ; il ne le déclenche apparemment qu’une fois qu’on commence à déplacer le slider.
      Merci pour le rapport, je corrigerai ça.
    • Sur Chromium sous Linux desktop, 100-100 → 99-100 → 99-99 fonctionne bien.
      En revanche, il y a un autre bug visuel. Si l’on part de 23-25 et qu’on fait glisser vers 24-25, la poignée de droite, à 25, bouge de quelques pixels. Si l’on remet la poignée de gauche à 23, la poignée de droite revient aussi.
    • C’est pour ça que j’aime créer des outils de développement.
  • Super ! J’ai toujours pensé qu’il fallait une implémentation de saisie de plage à deux valeurs pour les input HTML, parce que ça a beaucoup de sens.
    Par exemple, un cas d’usage serait : « je veux filtrer les produits qui ont au moins X widgets et au plus Y widgets ».
    Cela dit, j’ai trouvé un bug que je voudrais signaler dans Firefox. Chaque fois qu’on clique sur la poignée min ou max, la valeur change d’un cran vers le haut ou vers le bas.
    Si l’on essaie de cliquer exactement au centre de la poignée, la valeur ne change pas, mais si l’on clique un peu à gauche, la sélection diminue d’un cran, et si l’on clique un peu à droite, elle augmente.
    Le comportement attendu serait que la valeur ne change que lors d’une action de modification explicite, c’est-à-dire en maintenant le bouton enfoncé et en déplaçant réellement le pointeur de la souris.

    • Honnêtement, je ne pense pas que ce soit un bug. Tous les exemples sont assez denses, et les poignées couvrent plusieurs crans de la piste.
      Firefox interprète ce type de clic comme un clic direct sur la piste. Cette bibliothèque n’intervient pas du tout là-dessus ; c’est simplement le comportement de Firefox.
  • Le fait que cela fonctionne au clavier est excellent. Ça marche correctement, et si l’on maintient Shift-Tab, on peut aussi revenir en arrière.
    C’est vraiment bien fait, et même si c’est un tout petit détail, merci d’avoir implémenté cette interaction correctement.

  • Dans le design system de notre entreprise aussi, un stagiaire avait réalisé une preuve de concept avec deux éléments côte à côte dans ce style, mais il y avait toujours quelques pixels qui sautaient sur l’un des input, et nous n’avons finalement jamais réussi à le faire correctement.
    Un deuxième stagiaire a donc proposé une solution avec deux input range superposés.

  • Chaque fois que j’avais besoin d’une saisie de plage, j’utilisais le slider de jQuery UI, et il fallait ajouter jQuery et jQuery UI à la page juste pour un slider. Celui-ci est vraiment bien.
    Édit : serait-il possible de fournir à l’avance un fichier JS classique directement utilisable au lieu d’un module ESM ?

  • Je ne sais pas si l’on peut signaler des bugs ici. Les poignées des deux premiers exemples interceptent les interactions tactiles et empêchent le défilement. Les trois exemples suivants ne le font pas.
    Comme effet secondaire, on ne peut pas faire glisser vers le haut pour rendre visible le libellé sous le doigt ; la page défile et le libellé reste sous le doigt.

    • Si tu peux le reproduire, ce serait bien d’ouvrir une issue GitHub [1].
      Je serais curieux de savoir quel navigateur et quel système d’exploitation tu utilises. Cette bibliothèque utilise des input natifs, donc elle ne fait rien concernant le blocage du défilement.
      Sur mon téléphone iOS, le défilement est bloqué pour tous les éléments input[type=range], qu’ils soient stylés ou non.
      [1] https://github.com/Stanko/dual-range-input/issues
  • La partie « native » se discute. L’idée est que la bibliothèque utilise deux input range HTML natifs, ce qui préserve les interactions natives et les fonctionnalités d’accessibilité, mais pour moi le critère est clair.
    Si JavaScript est nécessaire pour que ça fonctionne comme prévu, alors ce n’est plus natif.

    • Je suis d’accord. Il me semble que c’est généralement ainsi que le terme est utilisé.
      Cela dit, je comprends l’intention de l’auteur. Comme il combine des sliders ordinaires au lieu d’implémenter lui-même un tas de div animées en JS, on est beaucoup plus proche d’une solution native. Si du CSS malin ne peut pas remplacer le JavaScript, c’est probablement l’approche la plus proche possible.
      Que ce soit natif ou non, ce genre de widget est très utile quand on peut l’utiliser, et il manquait des implémentations dans ce domaine.
    • C’est généralement vrai, mais la séparation n’est pas parfaitement nette. L’élément est natif, mais il y a des cas où il faut utiliser JavaScript pour l’ouvrir en état modal.
      Avant l’attribut popover, on ne pouvait tout simplement pas l’ouvrir sans JavaScript.
      Il existe aussi des méthodes JavaScript vanilla natives qu’il est utile de distinguer des bibliothèques qui n’utilisent pas du tout les outils de validation internes du navigateur, par exemple pour la validation de formulaires.
  • Je cherchais quelque chose comme ça quand j’ai créé https://spodj.intermer.net/.
    J’ai dû bricoler maladroitement avec React pour obtenir la fonctionnalité dont j’avais besoin, parce que je ne suis pas très bon en développement front-end.