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

Mariko Kosaka

A entrada está chegando ao Compositor

Esta é a última da série de quatro postagens do blog sobre o Chrome: investigando como ele lida com nosso código para exibir um site. Na postagem anterior, analisamos o processo de renderização e aprendemos sobre o compositor. Nesta postagem, abordaremos como o compositor permite uma interação suave quando a entrada do usuário é recebida.

Eventos de entrada do ponto de vista do navegador

Ao ouvir "eventos de entrada", você pode pensar apenas em digitar na caixa de texto ou no clique do mouse. No ponto de vista do navegador, a entrada significa qualquer gesto do usuário. A rolagem da roda do mouse é um evento de entrada, e o toque ou o cursor do mouse também é um evento de entrada.

Quando ocorre um gesto do usuário, como um toque na tela, o processo do navegador é aquele que recebe o gesto inicialmente. 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 dele ao processo do renderizador. O processo do renderizador processa o evento de forma adequada, encontrando o destino do evento e executando os listeners de eventos que estão anexados.

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

O composto recebe eventos de entrada

Figura 2: janela de visualização passando sobre as camadas da página

Na postagem anterior, vimos como o compositor pode lidar com a rolagem sem problemas com a composição de camadas rasterizadas. Se nenhum listener de eventos 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 descobrirá se o evento precisa ser tratado?

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

Como a execução do JavaScript é a função 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 de rolagem não rápida". Com essas informações, a linha de execução do compositor pode garantir que 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 continuará com a composição do novo frame sem esperar pela linha de execução principal.

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

Tenha cuidado ao criar manipuladores de eventos

Um padrão comum de manipulação de eventos no desenvolvimento da Web é a delegação de eventos. Como os eventos são balões, é possível anexar um manipulador de eventos ao elemento de nível mais alto e delegar tarefas com base no destino do evento. Talvez você já tenha visto ou escrito um código como o abaixo.

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

Como você só precisa criar um manipulador de eventos para todos os elementos, a ergonomia desse padrão de delegação de eventos é atrativa. No entanto, se você analisar esse código do ponto de vista do navegador, agora a página inteira está marcada como uma região de rolagem não 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 aguardar sempre que um evento de entrada chegar. Assim, a capacidade de rolagem suave do compositor é prejudicada.

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

Para atenuar isso, transmita opções de passive: true no listener de eventos. Isso indica para o navegador que você ainda quer detectar o evento na linha de execução principal, mas o compositor também pode compor o novo frame.

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

Conferir se é possível cancelar o evento

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

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

O uso da opção passive: true no evento de ponteiro significa que a rolagem da página pode ser suave, mas a rolagem vertical pode ter começado no momento que você quer preventDefault para limitar a direção de 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 verifica os registros de pintura, perguntando o que está 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 hit 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.

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

Na postagem anterior, discutimos como nossa 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 de tela touchscreen típico entrega eventos de toque de 60 a 120 vezes por segundo, e um mouse comum entrega eventos 100 vezes por segundo. O evento de entrada tem maior fidelidade do que a tela consegue 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 a execução de 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 o excesso de chamadas para a linha de execução principal, o Chrome une os eventos contínuos (como wheel, mousewheel, mousemove, pointermove, touchmove) e atrasa o envio até antes da próxima requestAnimationFrame.

eventos agrupados
Figura 8: a mesma linha do tempo de antes, mas o evento está sendo combinado e atrasado

Todos os eventos discretos, como keydown, keyup, mouseup, mousedown, touchstart e touchend, são enviados imediatamente.

Usar getCoalescedEvents para acessar eventos intraframe

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

getCoalescedEvents
Figura 9: caminho do gesto de toque suave à esquerda, caminho limitado combinado à 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 um atributo async na tag de script, esperamos que esta série tenha esclarecido por que um navegador precisa dessas informações para fornecer uma experiência da Web mais rápida e tranquila.

Usar o Lighthouse

Se você quer que seu código funcione bem com o navegador, mas não tem ideia por onde começar, o Lighthouse é uma ferramenta que executa auditorias de qualquer site e gera um relatório sobre o que está sendo feito certo e o que precisa ser melhorado. Ler a lista de auditorias também dá uma ideia do que é importante para um navegador.

Saiba como avaliar a performance

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

Adicionar política de recursos ao seu site

Se você quiser seguir uma etapa extra, a Política de recursos é um novo recurso da plataforma da Web que pode ser uma proteção durante a criação do projeto. Ativar a política de recursos garante o determinado comportamento do app e evita que você cometa erros. Por exemplo, para garantir que o app nunca bloqueie a análise, execute-o usando a política de scripts síncronos. Quando sync-script: 'none' estiver ativado, a execução de JavaScript de bloqueio do analisador será impedida. Isso evita que qualquer 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, quase só me importava com a forma como escreveria meu código e o que me ajudaria a ser mais produtivo. Essas coisas são importantes, mas também devemos pensar em como o navegador processa o código que escrevemos. Os navegadores modernos têm investido e continuam a investir em maneiras de fornecer uma melhor experiência da Web para os usuários. Ser gentil com o navegador organizando nosso código, por sua vez, melhora a experiência do usuário. Espero que você se junte a mim na missão de ser legal com os navegadores.

Agradecemos muito a todos que revisaram os primeiros rascunhos desta série, incluindo, entre outros: Alex Russell, PaulIrland, Meggin Kearney, Eric Bidelman, Mathias Bynens, Addy Osmani, Kinuko Nakokovo, 14}

Você gostou dessa série? Se você tiver dúvidas ou sugestões para a próxima postagem, envie um e-mail para você na seção de comentários abaixo ou para @kosamari no Twitter.