2 pontos por GN⁺ 2024-12-06 | 1 comentários | Compartilhar no WhatsApp
  • @stanko/dual-range-input combina dois inputs range HTML nativos para criar um slider de intervalo duplo, aproveitando as interações padrão e a acessibilidade do navegador
  • A implementação se concentra em cerca de 50 linhas de JavaScript para sincronizar os valores de entrada e em CSS, tornando-a mais simples do que abordagens tradicionais que recriam manualmente o arraste e a acessibilidade
  • Os dois inputs têm min, max e largura atualizados de acordo com o ponto médio dos valores selecionados; ao clicar na trilha, o thumb mais próximo se move para esse valor
  • Como a trilha do input range é mais curta que a largura real do input, a largura do thumb é adicionada ao cálculo, e o padding do wrapper absorve a largura excedente
  • O tema pode ser ajustado com variáveis CSS e gradient, permitindo sobrescrever a cor do intervalo selecionado, o tamanho do thumb, a cor da track etc.

Slider duplo feito com input range nativo

  • @stanko/dual-range-input implementa uma entrada de intervalo duplo colocando dois inputs range HTML lado a lado
  • Aqui, “nativo” não significa um único controle do navegador, mas sim que dois inputs range são usados para preservar as interações padrão e os recursos de acessibilidade do navegador
  • A implementação consiste em cerca de 50 linhas de JavaScript para sincronizar os inputs e CSS para o acabamento visual
  • O ponto de partida foi a necessidade de uma UI para ajustar os valores mínimo e máximo de parâmetros em uma ferramenta de desenho generativo
  • Havia dois requisitos
    • Usar input range HTML nativo
    • Ao clicar na trilha, o thumb mais próximo entre os dois deve se mover para aquele valor

Sincronização de valores e tratamento de cliques

  • Quando um dos dois inputs muda, a biblioteca calcula o ponto médio entre os dois valores selecionados
    • Define os atributos min e max de cada input de acordo com o ponto médio
    • Também atualiza a largura dos dois inputs com base no ponto médio
  • Como os inputs são redimensionados para se encontrarem no ponto médio, ao clicar entre os dois thumbs, o thumb mais próximo da posição do clique se move
    • Se o número de steps entre os dois thumbs for ímpar, o input usado por último tem prioridade

Cálculos de CSS e estilização

  • A trilha de um input range é mais curta que a largura total do input, e o navegador deixa espaço nas laterais para que o thumb não ultrapasse os limites
    • Se a largura dos inputs for simplesmente dividida como 25% e 75%, a proporção real da trilha não fica correta
    • A implementação adiciona --dri-thumb-width à largura de cada input e adiciona padding ao wrapper para acomodar a largura excedente
  • A estilização usa os recursos de CSS do navegador para estilizar inputs range
    • A track e o thumb são estilizados, e a parte central onde as duas tracks se encontram é tratada para não ter border radius
  • Temas podem ser criados por meio de sobrescrita de variáveis CSS
    • As principais variáveis incluem --dri-height, --dri-thumb-width, --dri-thumb-color, --dri-track-color, --dri-track-filled-color, --dri-track-border-radius etc.
  • O intervalo selecionado é preenchido com CSS gradient
    • A posição do gradient é definida pela variável --dri-gradient-position
    • Quando a largura do input é atualizada no código, essa variável também é atualizada
  • Essa implementação adota uma abordagem de primeiro aproveitar ao máximo os elementos nativos, em vez de recriar arraste e acessibilidade com uma biblioteca customizada

1 comentários

 
GN⁺ 2024-12-06
Comentários no Hacker News
  • Acho que esse tipo de slider deveria sempre permitir arrastar também a área central. Ou seja, ao pegar no meio, os dois manipuladores deveriam se mover juntos
    Para referência, é assim que o Unity trata isso: https://docs.unity3d.com/StaticFiles/ScriptRefImages/EditorG...

    • Por outro lado, eu jamais teria pensado em tentar clicar na área central. Na minha cabeça, isso parecia não um widget completamente novo, mas um slider modificado
      “Sempre” talvez seja uma palavra um pouco forte demais
  • Sou o autor. Fico feliz de ver isso no HN
    Vi que começaram a deixar issues e relatórios de bugs por aqui; já está tarde onde estou, então pretendo dar uma olhada amanhã
    Criei isso porque precisava para trabalhos com imagens generativas, e prefiro arrastar o slider e ver como a imagem muda a digitar números
    Tentei mantê-lo o mais minimalista possível

    • Gostei muito do artigo. Tomei meu café da manhã lendo com prazer sobre o processo de cálculo e a explicação do modo de depuração, e achei uma forma inteligente de aproveitar componentes nativos
      Hoje estou usando SwiftUI, onde é quase fácil demais criar componentes próprios. Mas, como o autor explicou, isso nem sempre é o melhor, e componentes nativos muitas vezes já são bem completos
      Também testei o artigo no Firefox para iOS, que é um navegador meio peculiar, e funcionou bem. Muito bem feito
  • Fui testar e encontrei um bug na hora. Depois de arrastar o slider para 100 - 100, não dá para deixá-lo em 99 - 99
    Também não dá para chegar lá a partir de 99 - 100, nem de 98 - 99. A única forma de chegar a 99 - 99 é puxar o limite inferior para menos de 98
    Se, a partir de 98 - 99, você baixar para 97 - 99, aí dá para ir para 99 - 99
    Esse bug pode acontecer em qualquer lugar; 99 - 99 só é o mais fácil de reproduzir por estar perto do fim do slider. Em geral, se você está em algum estado X - X e move uma das extremidades em 1 passo, não consegue arrastar a outra extremidade para dentro desse vão. Para fechar o intervalo, primeiro precisa arrastar a outra extremidade para mais longe

    • Boa descoberta! Na verdade eu tinha pensado nesse caso e ele funciona no Chrome e no Firefox, mas parece que o Safari não dispara o evento de focus no mouse down; só dispara quando o slider começa a se mover
      Obrigado pelo relatório, vou corrigir com certeza
    • No Chromium em Linux desktop, 100-100 → 99-100 → 99-99 funciona bem
      Mas há outro bug visual. Se você começa em 23-25 e arrasta para 24-25, o manipulador direito em 25 se move alguns pixels. Quando você leva o manipulador esquerdo de volta para 23, o direito também volta
    • É por isso que gosto de criar ferramentas de desenvolvedor
  • Legal! Sempre achei que inputs HTML precisavam de uma implementação de entrada de intervalo com dois valores, porque faz bastante sentido
    Por exemplo, há casos de uso como “quero filtrar produtos que tenham no mínimo X e no máximo Y widgets”
    Mas encontrei um bug no Firefox que gostaria de destacar. Toda vez que clico no manipulador de mínimo ou máximo, o valor muda um passo para cima ou para baixo
    Se eu tento clicar exatamente no centro do manipulador, o valor não muda, mas se clico um pouco à esquerda a seleção diminui um passo, e se clico um pouco à direita ela aumenta
    O comportamento esperado é que o valor só mude quando houver uma ação explícita de alteração, mantendo o botão pressionado e realmente movendo o ponteiro do mouse

    • Sinceramente, não acho que isso seja bug. Todos os exemplos são bem densos, e os manipuladores cobrem vários passos da trilha
      O Firefox interpreta esse clique como se fosse um clique direto na trilha. Esta biblioteca não interfere nisso; é simplesmente o modo como o Firefox se comporta
  • É excelente que funcione pelo teclado. Funciona corretamente, então também dá para voltar mantendo Shift-Tab pressionado
    Ficou muito bem feito, e agradeço por terem implementado corretamente essa interação, por menor que seja o detalhe

  • No design system da nossa empresa, um estagiário também criou uma prova de conceito com inputs lado a lado desse tipo, mas sempre havia pixels de um input pulando um pouco, e no fim não conseguimos deixar direito
    Então um segundo estagiário propôs uma solução usando 2 inputs range sobrepostos

  • Toda vez que eu precisava de um input de intervalo, usava o slider do jQuery UI, e precisava adicionar jQuery e jQuery UI à página só por causa de um slider. Isso é muito bom
    Edit: haveria possibilidade de fornecer previamente um arquivo JS comum pronto para uso, em vez de um módulo ESM?

  • Não sei se posso relatar bugs por aqui. Os manipuladores dos dois primeiros exemplos interceptam a interação por toque e impedem a rolagem. Os três exemplos seguintes não fazem isso
    Como efeito colateral, não dá para arrastar para cima para ver o rótulo sob o dedo; a página rola e o rótulo continua debaixo do dedo

    • Se conseguir reproduzir, seria ótimo abrir uma issue no GitHub [1]
      Fiquei curioso para saber qual navegador e sistema operacional você usa. Esta biblioteca usa inputs nativos, então não faz nada relacionado a bloquear rolagem
      No meu celular iOS, a rolagem é bloqueada em todos os elementos input[type=range], com ou sem estilo aplicado
      [1] https://github.com/Stanko/dual-range-input/issues
  • A parte de “nativo” é discutível. Dizem que a biblioteca usa dois inputs range nativos de HTML, preservando interações nativas e recursos de acessibilidade, mas meu critério é claro
    Se precisa de JavaScript para funcionar como pretendido, então já não é nativo

    • Concordo. Acho que geralmente o termo é usado assim mesmo
      Ainda assim, entendo a intenção do autor. Como ele combinou sliders comuns em vez de implementar um monte de divs movidas por JS, ficou muito mais próximo de uma solução nativa. Se não der para substituir o JavaScript por CSS inteligente, talvez seja o mais perto possível
      Independentemente de ser nativo ou não, esse tipo de widget é muito útil quando disponível, e havia poucas implementações nessa área
    • Em geral sim, mas a divisão não é totalmente nítida. O elemento é nativo, mas em alguns casos é preciso usar JavaScript para abri-lo em estado modal
      Antes do atributo popover, nem dava para abrir sem JavaScript
      Também há métodos nativos em JavaScript vanilla que vale diferenciar de bibliotecas que não usam nenhuma das ferramentas internas de validação do navegador, como validação de formulários
  • Eu estava procurando algo assim quando criei https://spodj.intermer.net/
    Tive que usar React de um jeito meio desajeitado para obter a funcionalidade de que precisava, porque não sou bom em desenvolvimento frontend