Como tornar a rolagem por toque rápida por padrão

Dave tapuska
Dave Tapuska

Sabemos que a capacidade de resposta da rolagem é fundamental para o engajamento do usuário com um site em dispositivos móveis, mas os listeners de eventos de toque geralmente causam sérios problemas de desempenho de rolagem. O Chrome está resolvendo esse problema permitindo que listeners de eventos de toque sejam passivos (transmitindo a opção {passive: true} para addEventListener()) e enviando a API de eventos de ponteiro. Esses são ótimos recursos para direcionar novos conteúdos em modelos que não bloqueiam a rolagem, mas às vezes os desenvolvedores os consideram difíceis de entender e adotar.

Acreditamos que a Web deve ser rápida por padrão sem que os desenvolvedores precisem entender detalhes misteriosos do comportamento do navegador. No Chrome 56, os listeners de toque são definidos como passivos por padrão nos casos em que isso geralmente corresponde à intenção do desenvolvedor. Acreditamos que, ao fazer isso, podemos melhorar muito a experiência do usuário e, ao mesmo tempo, ter um impacto negativo mínimo nos sites.

Em casos raros, essa mudança pode resultar em rolagem não intencional. Isso geralmente é resolvido com facilidade aplicando um estilo touch-action: nenhum ao elemento em que a rolagem não deve ocorrer. Continue lendo para saber os detalhes, saber se você foi afetado e o que pode fazer a respeito.

Segundo plano: eventos canceláveis deixam sua página mais lenta

Se você chamar preventDefault() nos primeiros eventos touchmove ou touchstart, evitará a rolagem. O problema é que, na maioria das vezes, os listeners não chamam preventDefault(), mas o navegador precisa aguardar a conclusão do evento para ter certeza. Os "listeners de eventos passivos" definidos pelo desenvolvedor resolvem essa questão. Ao adicionar um evento de toque com um objeto {passive: true} como o terceiro parâmetro no manipulador de eventos, você informa ao navegador que o listener touchstart não chamará preventDefault() e que o navegador poderá executar a rolagem com segurança sem bloquear o listener. Exemplo:

window.addEventListener("touchstart", func, {passive: true} );

A intervenção

Nossa principal motivação é reduzir o tempo necessário para atualizar a tela depois que o usuário toca nela. Para entender o uso das funções touchstart e touchmove, adicionamos métricas para determinar com que frequência o comportamento de bloqueio de rolagem ocorria.

Analisamos a porcentagem de eventos de toque canceláveis enviados para um destino raiz (janela, documento ou corpo) e determinamos que cerca de 80% desses listeners são conceitualmente passivos, mas não foram registrados dessa forma. Considerando a dimensão desse problema, notamos uma grande oportunidade de melhorar a rolagem sem nenhuma ação do desenvolvedor, tornando esses eventos "passivos" automaticamente.

Isso nos levou a definir nossa intervenção como: se o destino de um listener touchstart ou touchmove é o window, document ou body, o padrão é passive como true. Isso significa que códigos como:

window.addEventListener("touchstart", func);

se torna equivalente a:

window.addEventListener("touchstart", func, {passive: true} );

Agora, as chamadas para preventDefault() dentro do listener serão ignoradas.

O gráfico abaixo mostra o tempo gasto pelo 1% de rolagem da parte de cima desde o momento em que um usuário toca na tela para rolar a tela até o momento em que a tela é atualizada. Esses dados são para todos os sites no Chrome para Android. Antes da intervenção ser ativada, 1% das rolagens levavam pouco mais de 400 ms. Isso foi reduzido para pouco mais de 250 ms no Chrome 56 Beta, uma redução de cerca de 38%. No futuro, esperamos tornar a passiva verdadeira como o padrão para todos os listeners touchstart e touchmove, reduzindo esse valor para menos de 50 ms.

Gráfico dos principais tempos de srollagem de 1%

Falha e orientação

Na grande maioria dos casos, não serão observadas falhas. No entanto, quando ocorre uma falha, o sintoma mais comum é que a rolagem acontece quando você não quer. Em casos raros, os desenvolvedores também podem notar eventos de click inesperados, quando preventDefault() estava ausente em um listener touchend.

No Chrome 56 e versões mais recentes, o DevTools registra um aviso quando você chama preventDefault() em um evento em que a intervenção está ativa.

touch-passive.html:19 Unable to preventDefault inside passive event listener due to target being treated as passive. See https://www.chromestatus.com/features/5093566007214080

O aplicativo pode determinar se essa ação pode ser atingida, verificando se a chamada de preventDefault teve algum efeito pela propriedade defaultPrevented.

Descobrimos que a maioria das páginas afetadas é corrigida com relativa facilidade, aplicando a propriedade CSS touch-action sempre que possível. Se você quiser impedir toda a rolagem e o zoom de um elemento no navegador, aplique touch-action: none a ele. Se você tiver um carrossel horizontal, considere aplicar touch-action: pan-y pinch-zoom a ele para que o usuário ainda possa rolar verticalmente e aplicar zoom normalmente. Já é necessário aplicar a ação de toque corretamente em navegadores como o Edge para computadores que oferecem suporte a eventos de ponteiro, não a eventos de toque. No Safari para dispositivos móveis e navegadores para dispositivos móveis mais antigos que não são compatíveis com a ação por toque, os listeners de toque precisam continuar chamando preventDefault mesmo quando ele for ignorado pelo Chrome.

Em casos mais complexos, pode ser necessário recorrer a uma das opções a seguir:

  • Se o listener touchstart chamar preventDefault(), faça com que preventDefault() também seja chamado nos listeners de touchend associados para continuar suprimindo a geração de eventos de clique e outros comportamentos de toque padrão.
  • Por último (e não recomendado), transmita {passive: false} para addEventListener() a fim de substituir o comportamento padrão. Você precisará detectar se o user agent é compatível com EventListenerOptions.

Conclusão

No Chrome 56, a rolagem começa substancialmente mais rápido em muitos sites. Esse é o único impacto que a maioria dos desenvolvedores notará como resultado dessa mudança. Em alguns casos, os desenvolvedores podem notar rolagem não intencional.

Embora ainda seja necessário fazer isso no Safari para dispositivos móveis, os sites não podem confiar em chamar preventDefault() dentro dos listeners touchstart e touchmove, já que não há mais garantias de que isso será mantido no Chrome. Os desenvolvedores precisam aplicar a propriedade CSS touch-action nos elementos em que a rolagem e o zoom precisam ser desativados para notificar o navegador antes que ocorra qualquer evento de toque. Para suprimir o comportamento padrão de um toque (como a geração de um evento de clique), chame preventDefault() dentro de um listener touchend.