Eventos de entrada alinhados

Dave tapuska
Dave Tapuska

Texto longo, leia o resumo

  • O Chrome 60 reduz a instabilidade diminuindo a frequência de eventos, melhorando a consistência do tempo para a renderização do frame.
  • O método getCoalescedEvents(), introduzido no Chrome 58, oferece a mesma riqueza de informações de eventos que você já tinha.

Fornecer uma experiência do usuário tranquila é importante para a Web. O tempo entre o recebimento de um evento de entrada e a atualização dos recursos visuais é importante, além de fazer menos trabalho. Nas últimas versões do Chrome, diminuímos a latência de entrada nesses dispositivos.

Para suavidade e desempenho, no Chrome 60, estamos fazendo uma mudança que faz com que esses eventos ocorram em uma frequência mais baixa, aumentando a granularidade das informações fornecidas. Assim como quando o Jelly Bean foi lançado e trouxe o Choreographer, que alinha as entradas no Android, estamos trazendo as entradas alinhadas ao frame para a Web em todas as plataformas.

Mas, às vezes, você precisa de mais eventos. Portanto, no Chrome 58, implementamos um método chamado getCoalescedEvents(), que permite que seu aplicativo recupere o caminho completo do ponteiro, mesmo enquanto recebe menos eventos.

Vamos falar sobre a frequência de eventos primeiro.

Como diminuir a frequência de eventos

Vamos entender alguns conceitos básicos: telas sensíveis ao toque fornecem entrada de 60 a 120 Hz, e mouses enviam entradas normalmente a 100 Hz (mas podem chegar a até 2.000 Hz). No entanto, a taxa típica de atualização de um monitor é de 60 Hz. Então, o que isso realmente significa? Isso significa que as entradas são recebidas em uma taxa maior do que a atualização da tela de fato. Vamos conferir uma linha do tempo de desempenho do DevTools para um app de pintura de telas simples.

Na imagem abaixo, com a entrada alinhada requestAnimationFrame() desativada, é possível ver vários blocos de processamento por frame com um tempo para a renderização do frame inconsistente. Os pequenos blocos amarelos indicam testes de hits para, por exemplo, o destino do evento DOM, o envio do evento, a execução de JavaScript, a atualização do nó flutuante e, possivelmente, o novo cálculo do layout e dos estilos.

Linha do tempo de desempenho mostrando um tempo para a renderização do frame inconsistente

Por que estamos fazendo um trabalho extra que não causa atualizações visuais? O ideal é não fazer nenhum trabalho que não beneficie o usuário. A partir do Chrome 60, o pipeline de entrada vai atrasar o envio de eventos contínuos (wheel, mousewheel, touchmove, pointermove, mousemove) e os enviar antes da ocorrência do requestAnimationFrame() callback. Na imagem abaixo (com o recurso ativado), é possível ver um tempo para a renderização do frame mais consistente e menos eventos de processamento.

Estamos realizando um experimento com esse recurso ativado nos canais Canary e Dev e descobrimos que realizamos testes de hit 35% menores, o que permite que a linha de execução principal fique pronta para ser executada com mais frequência.

Uma observação importante de que os desenvolvedores da Web precisam estar cientes é que qualquer evento discreto (como keydown, keyup, mouseup, mousedown, touchstart, touchend) que ocorrer será enviado imediatamente com todos os eventos pendentes, preservando a ordenação relativa. Com esse recurso ativado, grande parte do trabalho é simplificada para o fluxo de loop de eventos normal, fornecendo um intervalo de entrada consistente. Isso traz eventos contínuos inline com eventos scroll e resize, que já foram otimizados no fluxo de loop de eventos no Chrome.

Linha do tempo de desempenho que mostra tempos para a renderização de frames relativamente consistentes.

Descobrimos que a grande maioria dos aplicativos que consomem esses eventos não têm utilidade para a frequência maior. O Android já alinhou eventos há vários anos, então nada é novo, mas os sites podem apresentar eventos menos granulares nas plataformas para computadores. Sempre houve um problema com linhas de execução principais instáveis, que causavam problemas na suavidade da entrada, o que significa que podem ocorrer saltos na posição sempre que o aplicativo está funcionando, o que torna impossível saber como o ponteiro passa de um ponto para outro.

Método getCoalescedEvents()

Como disse, há cenários raros em que o aplicativo prefere saber o caminho completo do ponteiro. Portanto, para corrigir o caso em que você vê grandes saltos e a frequência reduzida de eventos, no Chrome 58, lançamos uma extensão para eventos de ponteiro chamada getCoalescedEvents(). Confira abaixo um exemplo de como a instabilidade na linha de execução principal fica oculta do aplicativo se você usar essa API.

Comparar eventos padrão e agrupados.

Em vez de receber um único evento, é possível acessar a matriz de eventos históricos que o causaram. O Android, o iOS e o Windows têm APIs muito semelhantes nos SDKs nativos, e estamos expondo uma API semelhante na Web.

Normalmente, um app de desenho pode ter desenhado um ponto observando os deslocamentos no evento:

window.addEventListener("pointermove", function(event) {
    drawPoint(event.pageX, event.pageY);
});

Esse código pode ser facilmente alterado para usar a matriz de eventos:

window.addEventListener("pointermove", function(event) {
    var events = 'getCoalescedEvents' in event ? event.getCoalescedEvents() : [event];
    for (let e of events) {
    drawPoint(e.pageX, e.pageY);
    }
});

Nem todas as propriedades nos eventos agrupados são preenchidas. Como os eventos agrupados não são enviados, mas estão prontos para o passeio, eles não são testados. Alguns campos, como currentTarget e eventPhase, terão os valores padrão. Chamar métodos relacionados ao envio, como stopPropagation() ou preventDefault(), não terá efeito no evento pai.