Se é um botão, ele só tem uma função
(unsung.aresluna.org)- Mesmo um botão simples, como o de girar uma foto, pode mudar muito a experiência do usuário dependendo de como lida com entradas rápidas e repetidas
- O iPhone memoriza múltiplos comandos de rotação de 90 graus e executa a próxima rotação assim que a animação termina
- O Nothing Phone/Android confirma o toque com háptica e som, mas ignora toques subsequentes recebidos durante a animação de rotação
- Em situações em que é preciso girar várias fotos de documentos, até uma UI casual de edição de fotos precisa oferecer controles mais rápidos e previsíveis
- Em vez de fazer o usuário esperar até o fim da animação, o botão deveria aceitar a entrada por meio de buffering de toques ou interrompendo/acelerando a animação
Como o botão de rotação de fotos lida com entradas repetidas
- O botão de rotação de fotos é um controle padrão que, ao ser pressionado uma vez, normalmente faz uma rotação de 90 graus no sentido anti-horário e repete a mesma ação quando pressionado de novo
- A animação de rotação mostra o processo de mudança da orientação da foto, ajudando o usuário a não perder a noção da direção
- Se você pressionar rapidamente a rotação de 90 graus 8 vezes, a imagem deveria dar duas voltas e retornar à orientação original, como o “no op” de que engenheiros falam
- No iPhone, essa expectativa de fato se confirma
- Ele memoriza os toques rápidos
- Quando a rotação anterior termina, executa a próxima rotação pendente
- No Nothing Phone/Android, o resultado é diferente
- O botão oferece confirmação do toque por háptica e som
- Se a animação de rotação anterior ainda não tiver terminado, ele ignora o novo toque
Mesmo UIs casuais precisam de tratamento de entrada mais robusto em tarefas repetitivas
- Deficiência situacional é o conceito de que a deficiência não acontece apenas com algumas pessoas; qualquer pessoa pode, em determinada situação, vivenciar efetivamente uma deficiência
- Tirar fotos costuma estar mais perto de um uso casual, mas câmeras de celular podem confundir a orientação da foto quando se fotografa apontando para baixo
- Se você tirar várias fotos de documentos em modo paisagem e depois precisar girar cada uma, pode acabar tendo de rotacionar dezenas de fotos uma a uma
- Nessa situação, é mais agradável e menos trabalhoso que o botão se comporte de forma previsível mesmo quando pressionado três vezes na velocidade desejada
- Observando um período longo o bastante ou um grupo de usuários grande o bastante, interfaces casuais também encontram momentos em que precisam ser tratadas com mais seriedade
- Essa “condição situacional de power user” também aparece em casos como edição de fotos no celular ou GarageBand
- A solução não precisa necessariamente ser buffering de toques
- Quando chega um toque interruptivo, a animação também poderia ser interrompida ou acelerada
- O ponto central é não obrigar o usuário a esperar o fim da animação
2 comentários
Opiniões no Hacker News
O que fazer quando o usuário clica demais por engano, ou acredita que o primeiro clique não foi registrado?
Ainda me lembro de uma keynote em que Steve Jobs demonstrava como a exibição de documentos PDF tinha ficado mais rápida no novo macOS. Os engenheiros tinham preparado um botão que rolava o PDF na tela, mas Jobs apertou sem querer mais de uma vez e comentou: “Será que vai rolar até o fim duas vezes?” — e, de fato, a ação ficou em buffer. No fim, ele precisou voltar para cima e esperar a segunda rolagem terminar
Jobs contornou dizendo que “mesmo incluindo o tempo de percorrer o documento pela segunda vez, ainda é mais rápido que o PDF do OS anterior”
Debouncing existe por um motivo. Há casos em que, ao apertar um botão duas vezes, ele deve executar duas vezes; e há casos em que não deve. Distinguir qual é melhor em cada situação não é simples
No mínimo, é preciso pensar em qual tratamento faz sentido em cada contexto. Dentro de uma UI, alguns botões claramente pedem um comportamento, outros claramente pedem o oposto, mas alguns ficam ambíguos; se os dois comportamentos puderem ser defendidos, o resultado acaba sendo uma UI inconsistente
Não sei qual seria uma boa solução
Segundo o texto:
Esse é o problema. A quantidade de vezes que o app indica que a entrada do botão foi registrada deve ser igual ao número de ações realmente executadas. Debouncing é uma boa prática, mas, se for usado, não se deve dar feedback para toques que foram filtrados pelo debounce
A parte “e quando o usuário clica demais por engano, ou acredita que o primeiro clique não foi registrado?” me deixou bem confuso por trazer acessibilidade à discussão. Isso porque pensei imediatamente em uma pessoa com tremor nas mãos que tenta apertar uma vez e acaba apertando duas vezes
E também há pessoas que dão duplo clique em todos os botões. Para lidar com esse tipo de caso, é muito comum desativar o botão de envio no
onclickNão sou mais fanboy da Apple/Mac/Jobs como antigamente, mas sinto falta das apresentações da Apple que pareciam conduzidas por alguém que queria mostrar algo incrível
As apresentações recentes da Apple são rígidas demais, desumanizadas demais, e dão a impressão de serem operadas por pessoas de terno; nem consigo assistir até o fim. Todos parecem robôs escondendo emoção e humanidade por medo de serem demitidos se se desviarem do roteiro sagrado
Pode ser fugir do assunto, mas isso me fez lembrar de como até as apresentações improvisadas e meio bagunçadas de Jobs eram divertidas
Para dizer “mas e neste caso?”, esta situação claramente não se encaixa. Há dois motivos:
Quero apoiar o argumento de “e o debouncing?”, levantado em outro lugar, e o autor não deveria simplesmente ignorá-lo
Mas também não gosto do meme you had one job, e quero me opor ao uso irrefletido dele. Na maioria das vezes, esse meme está errado, e com botões geralmente também é assim. Em uma interface de usuário comum, um botão específico pode ter várias das seguintes funções ao mesmo tempo:
A expressão “uma única tarefa” não combina bem com botões. Um botão precisa fazer várias coisas, não apenas ser clicável
Há feedback quando ele é clicado, feedback ao passar o mouse por cima, estado de carregamento, estado desativado e combinações de tudo isso. Isso pode ser bastante frustrante. Por exemplo, se você aperta um botão ligado a um serviço de backend e não há nenhum feedback visual, não dá para saber se o botão foi pressionado. Se também não houver estado de carregamento, você não sabe se algo está sendo processado nos bastidores e acaba apertando várias vezes
O autor quer que, se clicar três vezes em qualquer velocidade, a mesma função sempre seja executada. Ele quer passar rapidamente pelas fotos e girar cada uma de forma previsível, o que é razoável
O autor usa acessibilidade como argumento
Mas também dá para imaginar um argumento totalmente oposto. Por exemplo, uma pessoa com deficiência motora ou Parkinson pode preferir um comportamento em que cliques excessivos sejam ignorados quando a intenção já foi definida
Fazer isso direito é complicado
No iOS há uma opção de acessibilidade chamada Ignore Repeats, que parece uma abordagem melhor por ser uma configuração do sistema inteiro. Pessoas que precisam desse tipo de correção podem se beneficiar também em lugares como o teclado na tela, e todas as outras pessoas não precisam digitar mais devagar
Não acho que todo widget de UI precise levar esse tipo de coisa em conta
Parece algo que poderia ser tratado como uma configuração do dispositivo inteiro. Por exemplo, ignorar toques repetidos dentro de 100 ms quando estão a menos de 50 px entre si
Concordo totalmente. Mesmo sem chegar ao Parkinson, ao ver pessoas que não são extremamente idosas, mas já têm certa idade, usando celulares e telas sensíveis ao toque, dá para perceber que muitas vezes é difícil apertar um botão pequeno no lugar exato e ter a sensação de “eu apertei”
Por isso, do ponto de vista de acessibilidade, acho que o comportamento do Nothing Phone se encaixaria muito melhor. Esses usuários não contam números e clicam 6 vezes para colocar uma imagem em uma posição específica. Além disso, também não faz muito sentido clicar 8 vezes de antemão para voltar exatamente à posição atual
O modo de pensar nesse caso é mais próximo de “clicar, esperar, comparar se está na posição desejada e clicar de novo”. Em contrapartida, um botão sensível que faz buffering da entrada tem grande chance de gerar overshooting, indo longe demais e depois voltando demais. É parecido com tentar rolar uma lista até o ponto desejado e errar continuamente
A abordagem do iPhone é mais adequada para pessoas jovens ou entusiastas de tecnologia que querem fazer tudo rapidamente sem esperar. É como em um teclado de computador, em que, por causa do atraso, você consegue digitar várias letras antes de a primeira aparecer na tela
Já usei um aparelho com botão físico; era um botão que fazia um bipe ao ser pressionado e adicionava 30 segundos ao tempo. Só que às vezes ele fazia o bipe, mas não adicionava os 30 segundos, e às vezes adicionava 30 segundos sem som. Então eu sempre tinha que apertar os olhos para enxergar o visor escuro e confirmar se tinha funcionado
Achei isso um design excepcionalmente ruim, mas desde então já vi botões puramente de software reproduzirem exatamente esse comportamento quebrado incontáveis vezes. A mudança de cor do botão na tela e a execução real da ação solicitada ficam apenas frouxamente conectadas. Por quê? Como é que implementaram isso?
Você escreve código imperativo que emite dois comandos, e ambos podem falhar de forma independente
Há muitas maneiras de fingir que isso foi “tratado”
Para começar, como todos os testes vão passar, a maioria dos desenvolvedores pode simplesmente parar de pensar nisso
Também dá para achar que basta capturar a exceção e só registrar no log, mas isso não corrige nada
Pode-se deixar rodando em produção por um tempo e ver se aparece algum problema. Quando finalmente aparecer, o desenvolvedor vai tentar de novo, e na segunda vez provavelmente vai funcionar, então ele pode parar de pensar nisso mais uma vez
Alguns dias atrás também houve uma grande discussão sobre o padrão outbox. A ideia é dividir a primeira coisa em duas metades e processá-las atomicamente, deixando a segunda coisa para o leitor
Acho que, no mundo real, esse problema continua aparecendo porque os desenvolvedores ficam em um estado de superposição quântica entre “isso não acontece”, “eu corrigi” e “não dá para corrigir”
O código que muda a cor do botão é um comportamento interno do componente “botão”, então não precisa ser implementado separadamente para cada botão. Mas isso significa que ele fica até certo ponto separado do código que executa a ação real. Se o manipulador de
on clicktiver uma verificação final como no texto original, do tipo “se a animação de rotação estiver em andamento, não rotacionar a imagem”, muitas vezes não há como informar ao botão que ele deve cancelar a mudança de cor. Por outro lado, mesmo que a animação de mudança de cor não seja reproduzida corretamente, o manipulador deon clickainda pode ser executadoNormalmente o botão muda de cor quando o botão do mouse é pressionado, mas a ação é executada quando o botão do mouse é solto. Esse método também dá intencionalmente a chance de cancelar no último momento arrastando para fora do botão enquanto mantém pressionado e, no mobile, às vezes reinterpreta a interação como rolagem em vez de clique. Em outras ocasiões, ele apenas cria mais oportunidades para a ação ser impedida depois que a cor já mudou
Isso é completamente independente de aquele pressionamento ser uma entrada útil no estado atual do software. Um software bem escrito deveria saber que não pode aceitar entrada e ter desabilitado o botão; e mesmo um software razoavelmente bem feito deveria fornecer feedback quase imediato de que está processando a ação ou de que ela foi cancelada
As pessoas frequentemente esquecem que animações têm um papel puramente auxiliar, e que a animação em si não é o objetivo
Animações existem para ocultar tempos de carregamento e fazer a transição suave de um estado para outro. É por isso que são usadas
Esse conhecimento acabou se perdendo, e agora temos código que precisa esperar a animação terminar
É mais um exemplo impressionante de culto à carga
Na verdade, não concordo com a palavra “ocultar”. Algo aparecer instantaneamente, sem nenhuma pista de por que apareceu, é claramente um antipadrão em telas sensíveis ao toque
A Apple antiga entendia que design era mais do que deixar algo bonito. A Apple atual com aparência de vidro só se importa com como as coisas aparecem em demos
Ao ver a primeira comparação, sinceramente achei que o problema estava no exemplo do iPhone. Cada vez que se toca no botão de girar, o botão e o slider abaixo da imagem desaparecem e depois fazem fade-in de novo; é um comportamento que já vi em vários apps do iOS e incomoda sem parar. Em especial, o app Captura de Tela é uma grande frustração para mim
Se um elemento de UX pode ser usado tanto antes quanto depois de uma interação, ele deveria continuar visível durante a transformação, o processamento ou o que for. Que ganho de UX há em esconder esses botões durante a rotação no iPhone? Nem fica melhor visualmente. Claro que, ultimamente, o software da Apple vem sacrificando melhorias reais de UX no altar da aparência
Dito isso, concordo com a afirmação do autor de que esses toques devem ser tratados independentemente da animação
Dá para criar algo parecido para UX. Seria uma coleção de restrições de padrões de design que emite alertas quando alguém tenta lançar algo com falhas conhecidas de UX
Lembrei de quando implementei o BabyButton, feito para um bebê usar. Em vez do comportamento normal de clique, usei o evento
touchdown, assim a criança podia ver imediatamente que algo aconteceu, e não havia problema se ela segurasse o botão por tempo demais ou movesse o dedo enquanto ainda estava pressionandoSe deveríamos criar sistemas para essa faixa etária é um assunto totalmente diferente, mas senti que foi um bom desafio de projetar algo adequado às necessidades do usuário
Mais de 99% dos botões no mundo real fazem algo assim que são pressionados. Mas, em uma GUI, um botão que se comportasse assim pareceria estranho
O texto de verdade que chega mais perto do ponto que o autor quer defender é este: https://aresluna.org/show-your-hands-honor/
Ao apertar o botão de ligar/desligar a tela do iPhone durante uma chamada, às vezes a chamada é encerrada e, outras vezes, apenas a tela apaga?
Ainda não entendi o padrão, mas às vezes já desliguei a chamada sem querer ao apertar esse botão antes de colocar o iPhone no bolso
Comentários no Lobste.rs
É uma provocação leve, mas, se estamos falando de boa UI/UX, um vídeo incorporado sem controles nem indicação é péssimo.
Muita gente deixa a reprodução automática desativada por padrão, e eu fiquei um bom tempo olhando para duas imagens quase iguais pensando: “tá, qual é a diferença?”
Concordo totalmente com “nunca faça o usuário esperar até a animação terminar”.
Em um app, o comando do usuário deve sempre ter prioridade sobre parecer bonito.
Acho que esse princípio também se aplica a jogos. Em alguns jogos, há um atraso muito curto entre abrir um menu e a entrada começar a ser registrada.
É o tipo de questão de, em um jogo com animações de menu vistosas, se ao apertar rapidamente pausa e seta para baixo o segundo item já deveria estar selecionado depois que o menu aparece.
Em jogos, bufferização de entrada é um tema delicado, mas, na navegação de menus, raramente parece ruim.
O vídeo não transmitia a informação muito bem. Era preciso prestar atenção para perceber o momento em que o botão era pressionado várias vezes seguidas.
Indo ao ponto principal, também é ambíguo se o celular Android está realmente fazendo algo errado. Entendo a frustração de querer apertar rapidamente 8 vezes para obter o resultado desejado, mas isso me lembra a detecção de duplo clique acidental em sistemas operacionais de desktop.
No fim, parece mais uma diferença entre o que o fabricante considera usável e o que o usuário sente como conveniente. Se for para escolher, no iPhone me parece mais dispersivo mostrar o botão “Done” mesmo durante a animação.
É difícil dizer que o Android está completamente errado. Já executei uma ação duas vezes por engano por causa de uma UI que respondia devagar.
Talvez a melhor solução seja desativar o botão até a animação terminar.
“Três cliques” não funciona, e cliques adicionais durante a animação são ignorados. Clicar, olhar para a tela enquanto espera a animação terminar, clicar de novo e esperar outra vez é lento e exige muita atenção do usuário.
Uma solução melhor parece ser dar feedback imediato na UI. Dá para renderizar imediatamente o estado do botão indicando que o clique foi processado, ou redesenhar a tela rapidamente de forma temporária antes da animação completa mais lenta, mostrando o progresso atual.
Concordo fortemente com o artigo inteiro. Eu diria que esse é um caso em que a animação viola o juramento de Hipócrates.
O primeiro princípio da animação deveria ser “antes de tudo, não causar dano”, ou seja, “antes de tudo, não interferir na entrada do usuário”.
Você já lidou com um encoder rotativo que não consegue acompanhar? O botão de volume do meu amplificador de fones de ouvido não faz nada se eu girar rápido demais.
Se eu fosse presidente por um dia...
Sempre que vejo algo assim, penso em como isso se comportaria no meu toolkit de GUI. Meu toolkit trata dois cliques rápidos como duplo clique, como um comportamento emprestado da plataforma Windows, e isso às vezes enlouquece a gente.
O duplo clique em si é uma convenção comum e tem valor como algo distinto do clique. Idealmente, os eventos de clique deveriam ser idempotentes, de modo que, mesmo disparados duas vezes, não causassem problemas. Por exemplo, se um clique seleciona um item e um duplo clique executa a ação padrão, selecionar o mesmo item duas vezes não faz mal. Mas, na prática, nem sempre é assim.
O problema é que, se você clicar muito rápido em um botão, o botão pode executar só uma vez, e o próximo evento ir para o duplo clique e acabar não fazendo nada. Assim surge um comportamento em que metade dos cliques é ignorada quando se clica rápido.
Por causa disso, criei um booleano chamado
triggersOnMultiClick; quando definido comotrue, ele emite eventos de trigger também em múltiplos cliques rápidos, como duplo clique. Mas, por compatibilidade retroativa, deixei o padrão comofalse; agora que está na hora de lançar uma nova versão major, considero o argumento do artigo válido e pretendo mudar o padrão paratrue.Dá para deixar algo como: “Adicionado em 2 de julho de 2021, padrão
false. Em 5 de julho de 2026 (v13.0), o padrão foi alterado paratrue, para que todos os cliques em botões disparem por padrão. Se quiser o comportamento antigo, sobrescreva o evento de duplo clique ou definatriggersOnMultiClick = falseapós chamar o construtor da superclasse.”Algo semelhante pode surgir por acaso na implementação de animações. Se você mantém uma variável de posição da animação dentro de um objeto e define o valor no clique, ao clicar duas vezes há basicamente duas opções: pular o restante da animação e ir direto para o próximo estado interno, ou ignorar o evento para preservar a suavidade visual, algo como
if(animationCounter) return;. Parece que o desenvolvedor do Android mencionado no artigo escolheu a segunda opção.No lado da Apple, provavelmente usaram uma terceira abordagem: manter uma fila de animações pendentes e inserir eventos nela. O que me deixa curioso é se o estado interno muda no momento em que entra na fila, no momento da conclusão, ou se a imagem é realmente rotacionada por animação. Se a animação for interrompida no meio e salva, será que sai uma imagem rotacionada em 45 graus? Se fosse eu, atualizaria atomicamente o EXIF em 90 graus e rodaria a animação de transição separadamente.
Pode haver várias escolhas razoáveis entre simplicidade de implementação, fidelidade visual e comportamento da UI. Pessoalmente, também gosto da abordagem em que o segundo toque não espera a animação e salta imediatamente para o novo estado. Acho que eu não chegaria a criar uma fila de animações.
É engraçado alterar o código em uma época parecida. A correção anterior também foi em 2 de julho, e esta agora, por acaso, é em 5 de julho. Gosto de escrever esse tipo de histórico diretamente no comentário da documentação. Depois, quando for mudar de novo, dá até para reler como lembrança.
A Apple provavelmente oferece uma fila de animações padrão, mas o momento da mudança de estado continua sendo uma questão separada. Na web, eu provavelmente atualizaria o estado de forma atômica e deixaria uma animação puramente visual separada, como uma transição CSS de
transform. Isso também evitaria recálculo de layout, então a implementação pareceria simples e eficiente. Quero testar depois se, ao trocar o nome da classe mais rápido do que a animação, ela salta ou entra em fila.Talvez seja por causa da idade, mas já houve algumas vezes em que esse comportamento me irritou quando dei dois toques sem querer.
Usar um botão no mesmo lugar ou um duplo toque como forma de confirmação é horrível. Se confirmação é realmente necessária, não se deve tornar fácil confirmar por acidente.
Ainda assim, neste exemplo, aceitar entradas rápidas em sequência parece uma boa ideia. Em outros casos, pode não ser. Há botões físicos que impedem intencionalmente apertar duas vezes até que algo “termine”, e, mesmo que nem sempre seja um design consciente, às vezes é a escolha certa.
Muita gente precisa usar dispositivos lentos ou defeituosos, e, se a pessoa tiver problemas de saúde como tremores nas mãos ou nos dedos, pode haver benefícios em limitar a entrada.
Este exemplo parece ser um caso em que os dois lados podem estar corretos. Há exemplos em que ter de esperar é mais irritante, e certamente há exemplos em que seria desejável haver limitação de entrada, seja por causa de animação ou de alguma outra condição.
É constantemente irritante quando os resultados de busca no navegador móvel são atualizados debaixo do meu dedo e empurram para outro lugar a opção que eu estava prestes a tocar.
Eu até me treinei a esperar mais, mas não faz sentido que a interação com a UI precise ser sensível a tarefas assíncronas em segundo plano cujo tempo de conclusão oscila por causa da qualidade da recepção.