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

Dave Tapuska
Dave Tapuska

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

Acreditamos que a Web precisa ser rápida por padrão sem que os desenvolvedores precisem entender detalhes obscuros do comportamento do navegador. No Chrome 56, estamos definindo o padrão de listeners de toque como passivos nos casos em que isso corresponde à intenção do desenvolvedor. Acreditamos que, ao fazer isso, podemos melhorar muito a experiência do usuário, com impacto mínimo negativo nos sites.

Em casos raros, essa mudança pode resultar em rolagem indesejada. Isso geralmente é fácil de resolver aplicando um estilo touch-action: none ao elemento em que a rolagem não deve ocorrer. Leia mais para saber detalhes, como saber se você foi afetado e o que fazer a respeito.

Contexto: os eventos canceláveis diminuem a velocidade da sua página

Se você chamar preventDefault() nos eventos touchstart ou touchmove, a rolagem será impedida. O problema é que, na maioria das vezes, os listeners não chamam preventDefault(), mas o navegador precisa esperar o evento terminar para ter certeza disso. Os "listeners de eventos passivos" definidos pelo desenvolvedor resolvem esse problema. Quando você adiciona um evento de toque com um objeto {passive: true} como o terceiro parâmetro no gerenciador de eventos, informa ao navegador que o listener touchstart não vai chamar preventDefault() e que o navegador pode executar o 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 de touchstart e touchmove, adicionamos métricas para determinar a frequência com que o comportamento de bloqueio de rolagem ocorreu.

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 como tal. Dada a escala desse problema, notamos uma grande oportunidade de melhorar a rolagem sem nenhuma ação do desenvolvedor, tornando esses eventos automaticamente "passivos".

Isso nos levou a definir nossa intervenção como: se o alvo de um listener de touchstart ou touchmove for window, document ou body, vamos definir passive como true por padrão. Isso significa que um código como:

window.addEventListener("touchstart", func);

fica 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% superior de rolagens desde o momento em que o usuário toca na tela para rolar até o momento em que a tela é atualizada. Esses dados são de todos os sites no Chrome para Android. Antes da intervenção ser ativada, 1% dos rolamentos levava 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 o padrão para todos os listeners touchstart e touchmove verdadeiro, reduzindo-o para menos de 50 ms.

Gráfico dos 1% dos tempos de rolagem

Interrupção e orientação

Na grande maioria dos casos, não há quebra. No entanto, quando ocorre um erro, o sintoma mais comum é que a rolagem acontece quando você não quer. Em casos raros, os desenvolvedores também podem notar eventos de clique inesperados (quando o preventDefault() está 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 app pode determinar se ele pode estar atingindo esse limite verificando se a chamada de preventDefault teve algum efeito na propriedade defaultPrevented.

Descobrimos que a grande maioria das páginas afetadas pode ser corrigida com relativa facilidade aplicando a propriedade touch-action do CSS sempre que possível. Se você quiser impedir a rolagem e o zoom do navegador em um elemento, aplique touch-action: none a ele. Se você tiver um carrossel horizontal, aplique touch-action: pan-y pinch-zoom a ele para que o usuário ainda possa rolar verticalmente e fazer zoom normalmente. Aplicar a ação de toque corretamente já é necessário em navegadores como o Edge para computador que oferecem suporte a eventos de ponteiro, mas não a eventos de toque. Para o Safari para dispositivos móveis e navegadores mais antigos que não oferecem suporte a ações de toque, seus listeners de toque precisam continuar chamando preventDefault, mesmo que ele seja ignorado pelo Chrome.

Em casos mais complexos, talvez seja necessário usar uma das seguintes opções:

  • Se o listener touchstart chamar preventDefault(), verifique se preventDefault() também é chamado pelos listeners de touchend associados para continuar suprimindo a geração de eventos de clique e outros comportamentos de toque padrão.
  • A última (e desencorajada) transmissão {passive: false} para addEventListener() para substituir o comportamento padrão. É necessário detectar se o User Agent oferece suporte a EventListenerOptions.

Conclusão

No Chrome 56, a rolagem começa muito mais rápido em muitos sites. Esse é o único impacto que a maioria dos desenvolvedores vai 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 devem depender de chamadas de preventDefault() dentro de listeners touchstart e touchmove, já que isso não é mais garantido no Chrome. Os desenvolvedores precisam aplicar a propriedade CSS touch-action em elementos em que a rolagem e o zoom precisam ser desativados para notificar o navegador antes que qualquer evento de toque ocorra. 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.