Análise detalhada de um navegador da Web moderno (parte 4)

Mariko Kosaka

A entrada está chegando ao compositor

Esta é a última parte da série de blogs de quatro partes que analisa o Chrome, investigando como ele processa nosso código para mostrar um site. Na postagem anterior, analisamos o processo de renderização e aprendemos sobre o compositor. Nesta postagem, vamos conferir como o compositor está permitindo uma interação suave quando a entrada do usuário chega.

Eventos de entrada do ponto de vista do navegador

Quando você ouve "eventos de entrada", pode pensar apenas em digitar em uma caixa de texto ou clicar com o mouse, mas, do ponto de vista do navegador, entrada significa qualquer gesto do usuário. A rolagem da roda do mouse é um evento de entrada, e o toque ou o passar do mouse também é um evento de entrada.

Quando um gesto do usuário, como tocar na tela, ocorre, o processo do navegador é o que recebe o gesto primeiro. No entanto, o processo do navegador só sabe onde esse gesto ocorreu, já que o conteúdo dentro de uma guia é processado pelo processo do renderizador. Assim, o processo do navegador envia o tipo de evento (como touchstart) e as coordenadas para o processo do renderizador. O processo do renderizador processa o evento de maneira adequada, encontrando o destino do evento e executando listeners de eventos anexados.

evento de entrada
Figura 1: evento de entrada roteado pelo processo do navegador para o processo do renderizador

O compositor recebe eventos de entrada

Figura 2: viewport paira sobre as camadas da página

Na postagem anterior, analisamos como o compositor poderia processar a rolagem de forma suave com a composição de camadas rasterizadas. Se nenhum listener de evento de entrada estiver anexado à página, a linha de execução do compositor poderá criar um novo frame composto completamente independente da linha de execução principal. Mas e se alguns listeners de eventos fossem anexados à página? Como a linha de execução do compositor descobriria se o evento precisa ser processado?

Noções básicas sobre a região de rolagem não rápida

Como a execução do JavaScript é o trabalho da linha de execução principal, quando uma página é composta, a linha de execução do compositor marca uma região da página que tem manipuladores de eventos anexados como "Região não rolável rápida". Com essas informações, a linha de execução do compositor pode enviar o evento de entrada para a linha de execução principal se o evento ocorrer nessa região. Se o evento de entrada vier de fora dessa região, a linha de execução do compositor vai continuar compondo um novo frame sem esperar pela linha de execução principal.

região limitada não rolável
Figura 3: diagrama da entrada descrita para a região não rolável rápida

Tenha cuidado ao escrever manipuladores de eventos

Um padrão comum de tratamento de eventos no desenvolvimento da Web é a delegação de eventos. Como os eventos flutuam, é possível anexar um gerenciador de eventos ao elemento principal e delegar tarefas com base no destino do evento. Você pode ter visto ou escrito um código como o abaixo.

document.body.addEventListener('touchstart', event => {
    if (event.target === area) {
        event.preventDefault();
    }
});

Como você só precisa escrever um gerenciador de eventos para todos os elementos, a ergonomia desse padrão de delegação de eventos é atraente. No entanto, se você analisar esse código do ponto de vista do navegador, a página inteira será marcada como uma região não rolável rápida. Isso significa que, mesmo que o aplicativo não se importe com a entrada de determinadas partes da página, a linha de execução do compositor precisa se comunicar com a linha de execução principal e esperar por ela sempre que um evento de entrada chegar. Assim, a capacidade de rolagem suave do compositor é anulada.

região sem rolagem rápida na página inteira
Figura 4: diagrama da entrada descrita para a região não rolável rápida que abrange uma página inteira

Para evitar que isso aconteça, transmita as opções passive: true no listener de eventos. Isso indica ao navegador que você ainda quer ouvir o evento na linha de execução principal, mas o compositor pode continuar e compor um novo frame também.

document.body.addEventListener('touchstart', event => {
    if (event.target === area) {
        event.preventDefault()
    }
 }, {passive: true});

Verificar se o evento é cancelável

rolagem de página
Figura 5: uma página da Web com parte dela fixada na rolagem horizontal

Imagine que você tem uma caixa em uma página que quer limitar a direção de rolagem apenas para rolagem horizontal.

Usar a opção passive: true no evento do ponteiro significa que a rolagem da página pode ser suave, mas a rolagem vertical pode ter começado no momento em que você quer preventDefault para limitar a direção da rolagem. É possível verificar isso usando o método event.cancelable.

document.body.addEventListener('pointermove', event => {
    if (event.cancelable) {
        event.preventDefault(); // block the native scroll
        /*
        *  do what you want the application to do here
        */
    }
}, {passive: true});

Como alternativa, use uma regra CSS como touch-action para eliminar completamente o manipulador de eventos.

#area {
  touch-action: pan-x;
}

Como encontrar o destino do evento

teste de hit
Figura 6: a linha de execução principal que analisa os registros de pintura perguntando o que é desenhado no ponto x.y

Quando a linha de execução do compositor envia um evento de entrada para a linha de execução principal, a primeira coisa a ser executada é um teste de acerto para encontrar o destino do evento. O teste de hit usa dados de registros de pintura gerados no processo de renderização para descobrir o que está abaixo das coordenadas do ponto em que o evento ocorreu.

Como minimizar os envios de eventos para a linha de execução principal

Na postagem anterior, discutimos como nossa tela de exibição típica atualiza a tela 60 vezes por segundo e como precisamos acompanhar a cadência para uma animação suave. Para entrada, um dispositivo touchscreen típico gera eventos de toque de 60 a 120 vezes por segundo, e um mouse típico gera eventos 100 vezes por segundo. O evento de entrada tem uma fidelidade maior do que a tela pode atualizar.

Se um evento contínuo, como touchmove, for enviado para a linha de execução principal 120 vezes por segundo, ele poderá acionar uma quantidade excessiva de testes de hit e execução do JavaScript em comparação com a velocidade de atualização da tela.

eventos não filtrados
Figura 7: eventos que inundam a linha do tempo do frame, causando instabilidade na página

Para minimizar chamadas excessivas na linha de execução principal, o Chrome combina eventos contínuos (como wheel, mousewheel, mousemove, pointermove e touchmove) e atrasa o envio até pouco antes do próximo requestAnimationFrame.

eventos agrupados
Figura 8: mesma linha do tempo anterior, mas o evento está sendo agrupado e atrasado

Qualquer evento discreto, como keydown, keyup, mouseup, mousedown, touchstart e touchend, é enviado imediatamente.

Use getCoalescedEvents para receber eventos intraframe

Para a maioria dos aplicativos da Web, os eventos agrupados são suficientes para oferecer uma boa experiência do usuário. No entanto, se você estiver criando coisas como um aplicativo de desenho e colocando um caminho com base nas coordenadas touchmove, poderá perder as coordenadas intermediárias para desenhar uma linha suave. Nesse caso, é possível usar o método getCoalescedEvents no evento do ponteiro para receber informações sobre esses eventos agrupados.

getCoalescedEvents
Figura 9: caminho do gesto de toque suave à esquerda e caminho limitado mesclado à direita
window.addEventListener('pointermove', event => {
    const events = event.getCoalescedEvents();
    for (let event of events) {
        const x = event.pageX;
        const y = event.pageY;
        // draw a line using x and y coordinates.
    }
});

Próximas etapas

Nesta série, abordamos o funcionamento interno de um navegador da Web. Se você nunca pensou em por que o DevTools recomenda adicionar {passive: true} ao seu manipulador de eventos ou por que você pode escrever o atributo async na tag de script, esperamos que esta série esclareça por que um navegador precisa dessas informações para oferecer uma experiência na Web mais rápida e suave.

Usar o Lighthouse

Se você quer que seu código seja bom para o navegador, mas não sabe por onde começar, o Lighthouse é uma ferramenta que realiza auditorias em qualquer site e gera um relatório sobre o que está sendo feito corretamente e o que precisa de melhorias. A leitura da lista de auditorias também dá uma ideia do que um navegador considera importante.

Saiba como medir a performance

Os ajustes de performance podem variar para sites diferentes. Por isso, é fundamental medir a performance do seu site e decidir o que é melhor para ele. A equipe do Chrome DevTools tem alguns tutoriais sobre como medir a performance do seu site.

Adicionar a política de recursos ao seu site

Se você quiser dar um passo extra, a política de recursos é um novo recurso da plataforma da Web que pode ser um limite para você ao criar seu projeto. Ativar a política de recursos garante o comportamento específico do app e evita erros. Por exemplo, se você quiser garantir que o app nunca bloqueie a análise, execute-o na política de scripts síncronos. Quando sync-script: 'none' está ativado, o JavaScript que bloqueia o analisador não pode ser executado. Isso impede que o código bloqueie o analisador, e o navegador não precisa se preocupar em pausar o analisador.

Conclusão

agradeço

Quando comecei a criar sites, eu só me preocupava em como escrever o código e o que me ajudaria a ser mais produtivo. Essas coisas são importantes, mas também precisamos pensar em como o navegador recebe o código que escrevemos. Os navegadores modernos têm investido e continuam investindo em maneiras de proporcionar uma experiência da Web melhor para os usuários. Ser gentil com o navegador organizando nosso código também melhora a experiência do usuário. Espero que você se junte a mim na missão de ser gentil com os navegadores.

Agradecemos a todos que revisaram os primeiros rascunhos desta série, incluindo (mas não se limitando a): Alex Russell, Paul Irish, Meggin Kearney, Eric Bidelman, Mathias Bynens, Addy Osmani, Kinuko Yasuda, Nasko Oskov e Charlie Reis.

Você gostou da série? Se você tiver dúvidas ou sugestões para a próxima postagem, entre em contato comigo na seção de comentários abaixo ou pelo @kosamari no Twitter.