Critical rendering path - Renderização no browser.
Como o browser renderiza? quais os passos que ele segue até exibir a página na tela?
Índice
Caminho crítico de renderização
O caminho crítico de renderização é a sequência de etapas que o navegador executa para converter o HTML, CSS e Javascript em pixels na tela. Vamos conhecer essas etapas e como podemos renderizar as nossas páginas mais rápido.
As etapas que o browser realiza para renderizar nossa página são:
- Pegar o HTML e começar a criar o Document Object Model. (DOM)
- Extrair o CSS e criar o Css Object Model (CSSOM)
- Combinar o DOM e CSSOM para criar a árvore de renderização. (Rendering tree)
- Descobrir onde tudo se encaixa na página (Layout)
- Pintar os pixels na tela (Paint)
Também temos a etapa de como o browser lida com o javascript, falaremos sobre isso mais à frente.
Convertendo HTML para DOM
Quando acessamos uma página da web, o browser recebe a resposta da nossa requisição (conteúdo HTML) e transforma em pixels, vamos descobrir como isso é feito.
A especificação do HTML
possui uma série de regras sobre como o browser deve processar o conteúdo recebido, por exemplo, o texto contido entre sinais de menor <
e maior >
possui um significado especial no HTML
, isso deve ser considerado uma TAG
, como resultado, toda vez que o browser encontra uma TAG
ele emite um Token
, vejamos um exemplo:
<html> <head> <meta name="viewport" content="width=device-width" /> <link href="style.css" rel="stylesheet" />
... </head></html>
Após o browser receber o nosso html ele analisa a primeira TAG <html>
e emite um token, existem dois tipos de tokens
o StartTag
e o EndTag
, nesse caso, o primeiro token é o StartTag: HTML
, o segundo StartTag: head
e assim por diante. Esse processo inteiro é feito pelo tokenizer e enquanto o tokenizer está fazendo esse trabalho, existe outro processo que está consumindo esses tokens e convertendo em Node Objects
. Por exemplo: o browser converte o primeiro token StartTag: HTML
e cria o HTML Node
, depois consome o pŕoximo token e cria o node
correspondente. Perceba que o tokenizer emite Start
e End
tokens, que nos mostra a relação entre os nós (nodes
). O token StartTag: head
vem antes do token EndTag: HTML
o que nos diz que o token head é um filho do token html, nessa lógica, vemos que o node da tag meta
e o node da tag link
são filhos de head, e assim por diante.
Veja um esquema desse processo aplicado à uma página web simples:
Depois de consumido todos os tokens e todos os nodes criados, chegamos ao DOM, que é a estrutura em árvore que captura o conteúdo e propriedades do HTML e todas suas relações entre os nodes.
No DevTools do navegador Google Chrome existe uma aba chamada Performance nela é possível consultar o tempo necessário que o browser levou para executar todo esse processo que vimos acima:
O browser constrói o DOM incrementalmente, e nós podemos tirar vantagem disso e aumentar a velocidade da renderização. Um exemplo é como a página de resultados do Google trabalha, assim que você está digitando o que quer procurar o servidor do google já entrega o header da página, que é igual para todos os usuários, assim, quando você terminar de escrever o que deseja buscar, o servidor envia os dados em HTML e o browser incrementa o DOM com os resultados. Como você pode notar, o servidor do google não precisa esperar que você envie o que deseja buscar para retornar o HTML completo para o browser processar tudo de uma vez, ele começa com o header da página, e incrementalmente devolve o resultado da sua busca. Portanto, entregar HTML em parcelas é algo muito bom!
CSSOM - Convertendo CSS em CSS Object Model
Bom, o DOM captura o conteúdo da página, mas nós também precisamos saber como exibi-la e onde encaixar as coisas, e para isso, o browser precisa criar o CSS Object Model, o CSSOM é bem parecido com o DOM, porém, DOM e CSSOM são estruturas independentes! Vejamos:
Quando o navegador está construindo o DOM e encontra uma tag link
que referencia alguma folha de estilo CSS ele prevê que esse recurso é necessário para renderizar a página e envia imediatamente uma requisição para esse arquivo, segue um exemplo do que essa folha de estilos pode retornar:
body { font-size: 16px;}p { font-weight: bold;}span { color: red;}p span { display: none;}img { float: right;}
Assim como o HTML o navegador precisa converter as regras CSS em algo que ele consiga entender e utilizar. Portanto, o processo feito no HTML é repetido, só que dessa vez, para o CSS.
Como vemos, segue o mesmo padrão utilizado no HTML, só que ao invés de criar a estrutura do DOM, é criada a estrutura do CSSOM.
Com o esquema acima, conseguimos entender o porque o CSS é aplicado em cascata. O estilo aplicado ao elemento body
é herdado por todos os seus filhos, analisando a árvore CSS acima, todo texto dentro do elemento span
terá fonte tamanho 16 pixels (estilo herdado do elemento body
) e a cor vermelha (estilo específico para o elemento span
).
Há um detalhe muito importante, todo navegador possui um conjunto de regras CSS padrões, conhecidos também por estilos de user-agent (por exemplo estilos padrões do Internet Explorer)
Assim como é possível descobrir o tempo necessário para o parse do HTML, também é possível verificar o tempo necessário para o processamento do CSS através do DevTools:
É possível analisar o tempo decorrido e também quantos elementos foram afetados. Com isso, podemos concluir que escrever um CSS de qualidade, com seletores específicos, evitar sobreescritas e evitar !important é de grande importância, devemos afetar apenas os elementos corretos para diminuir o tempo necessário de processamento do CSSOM.
Até aqui vimos a construção dos modelos de objetos, criamos as árvores do DOM e do CSSOM. No entanto, ambos são objetos independentes: um descreve o conteúdo e o outro, as regras de estilos que devem ser aplicadas ao documento. Para entender a mescla entre o DOM e o CSSOM precisamos conhecer a árvore de renderização
Árvore de renderização
A primeira tarefa que o browser efetua quando está nessa fase é a combinação do DOM e CSSOM em uma árvore de renderização que contém todo conteúdo visível do DOM e todas regras de CSS do CSSOM de cada nó.
Exemlo de uma árvore de renderização
As demais tarefas são as seguintes:
- A partir do começo do DOM, percorre cada elemento (nó) visível.
- Para cada nó visível é encontrada e aplicada a sua regra do CSS correspondente.
- Por fim, retorna os nós visíveis e seus estilos aplicados.
- Alguns elementos não são visíveis, como
head
,script
meta
, etc, e são omitidos pois não são refletidos no resultado da renderização. - Os elementos que forem ocultados via CSS também são omitidos no resultado da renderização, no exemplo acima, uma das tags
span
está omitida, porque existe uma regra CSS que define a propriedadedisplay: none
.
Observação: a regra CSS visibility: hidden
é diferente da regra display: none
. A primeira torna o elemento invisível, o elemento ainda ocupa o seu espaço no layout, ou seja, é renderizado como uma caixa vazia. O segundo remove completamente o elemento da árvore de renderização, o elemento fica invisível e não faz parte do layout.
Por fim, temos o resultado da renderização com o conteúdo e toda informação de estilo do conteúdo visível na tela. Podemos passar para fase de layout.
Layout
Até agora o browser calculou quais nós devem ser visíveis e processou seus estilos. O browser precisa agora calcular a posição e o tamanho que irá ocupar no viewport (espaço disponível do dispositivo), essa fase é o layout, também conhecida como reflow.
Para calcular a posição e o tamanho exato de cada elemento, o browser analisa a árvore de renderização passando por toda ela a partir de sua raiz. Vamos analisar esse simples exemplo:
<html> <head> <meta name="viewport" content="width=device-width,initial-scale=1" /> <title>Critial Path: Hello world!</title> </head> <body> <div style="width: 50%"> <div style="width: 50%">Hello world!</div> </div> </body></html>
O HTML
acima possuí dois divs aninhados. O primeiro (pai) define o tamanho do elemento na tela como 50% da largura da janela do dispositivo (viewport). O segundo div (filho) define sua largura como 50% do seu pai, ou seja, 25% da largura do viewport.
O resultado da renderização é em modelo de caixa, ou seja, todos elementos são retângulos. Todas as medidas relativas, como porcentagens, em’s, rem’s, etc são convertidas em pixels absolutos na tela.
Agora que o browser possui todos os nós visíveis, seus estilos aplicados e sua geometria, finalmente chega a última fase, essa etapa é chamada de Paint.
Paint
O paint é o processo que o browser executa para aplicar cada nó da árvore de renderização em pixeis reais na tela do dispositivo do usuário. Assim como os outros processos, no DevTools do Chrome, também é possível examinar o tempo necessário para a fase de Layout e Paint.
- O evento layout captura exibe o tempo e a quantidade de nós que foram construídos, posicionados e tiveram seu tamanho calculado a partir da árvore de renderização.
- Assim que o layout termina, o browser emite eventos de Paint setup e Paint, esses eventos convertem a árvore de renderização em pixeis na tela.
O tempo nececssário para executar essa renderização varia de acordo com a quantidade de elementos que temos em nosso HTML, da complexidade do CSS e do tamanho do viewport do dispositivo. Quanto maior o HTML, maior trabalho para o navegador. Quanto mais complexo o estilo, mais tempo é necessário na etapa de Paint. Fazer a renderização de uma cor sólida (#000
) é pouco custosa, porém, a renderização de uma sombra (box-shadow
) é bem mais complexa e custosa.
E com isso descobrimos todo o trabalho que o browser necessita realizar para renderizar nossa página na tela do usuário. No próximo post veremos o que gera o famoso “Bloqueio de renderização” ou “Bloqueio do caminho critíco de renderização” e como podemos com medidas simples, solucionar isso.
- Todas imagens foram retiradas do Google Developers