Saber mais sobre renderização de páginas web e como isso é importante para o desenvolvimento web é crucial — e espanta o fato de 100% dos desenvolvedores front-end não aprenderem isso logo nos primeiros dias de seus estudos. Vários artigos cobrindo o assunto estão disponíveis, mas a informação é dispersa e, de certa forma, fragmentada.
A renderização tem de ser otimizada desde o início, quando o layout da página está sendo definido, e ser tão crucial quanto o papel de estilos e scripts têm na renderização de páginas. É interessantíssimo que profissionais/estudiosos da área conheçam certos truques para evitar alguns problemas de desempenho.
Como navegadores renderizam página web
Resumidamente, estas são as ações de um browser ao renderizar uma página:
- O DOM (Document Object Model) é montado a partir do código HTML recebido de um servidor
- Os estilos são carregados e analisados (parsed), formando a CSSOM (CSS Object Model)
- Sobre DOM e CSSOM, uma árvore de renderização (rendering tree) é criada — um conjunto de objetos a serem renderizados, que podem ser chamados renderizador (renderer), objeto de renderização (render object), frame e outros, conforme a engine que se está trabalhando. Essa árvore de renderização reflete a estrutura do DOM — excetuando-se elementos invisíveis, como a tag
<head>
ou elementos estilizados comdisplay: none;
). Cada string de texto é representada na rendering tree como um processador separado. Cada um dos objetos de renderização contêm seu objeto correspondente no DOM (ou um bloco de texto) mais os estilos calculados. Em outras palavras, a árvore de renderização descreve a representação visual do DOM. - Para cada elemento da árvore, suas coordenadas são calculadas — o que é chamado de “layout”. Navegadores usam um método fluido que exige somente uma “passagem” para montar o layout de todos os elementos (com exceção de tabelas, que exigem mais do que uma passagem).
- Finalmente, tudo é efetivamente exibido em uma janela do navegador, num processo chamado de “pintura” (“painting”).
Quando as pessoas interagem com a página ou os scripts a modificam, algumas das operações acima mencionadas têm de ser repetidas, tal como mudanças na estrutura subjacentes da página.
Repaint
Ao mudar estilos de elementos que não afetam a posição desse elemento na página (como background-color
, border-color
, visibility
etc), o browser apenas “repinta” (repaint) o elemento com os novos estilos aplicados (que significa que um repaint ou restyle está acontecendo).
Reflow
Quando as alterações afetam o conteúdo ou estrutura do documento ou a posição de um elemento, um refluxo (reflow) ou relayout acontece. Essas alterações são geralmente desencadeadas por:
- Manipulação do DOM (adição de elemento, exclusão, alteração ou alterar na ordem dos elementos)
- Alterações de conteúdos, incluindo alterações de texto em campos do formulário
- Cálculos e/ou alterações de propriedades CSS
- Adição ou remoção de folhas de estilo
- Alteração no atributo
class
- Manipulação na janela do navegador (redimensionamento, rolagem etc)
- Ativação de pseudo-classe (
:hover
)
Como navegadores otimizam a renderização
Navegadores tentam fazer seu melhor para restringir repaint/reflow apenas para a área que abrange os elementos alterados. Por exemplo, uma mudança de tamanho em um elemento posicionado com absolute
ou fixed
afeta somente o próprio elemento e seus descendentes, enquanto uma mudança semelhante em um elemento static
(o padrão de posicionamento em CSS) provoca reflow em todos os elementos subsequentes.
Outra técnica de otimização usada pelos browsers é que, durante a execução de trechos de código JavaScript, eles armazenam as alterações e as aplicam em uma única passada depois que o código foi executado. Por exemplo, este trecho de código só irá desencadear um reflow e repaint (amostras de código a seguir usando jQuery):
1 2 3 4 5 6 |
var $body = $('body'); // somente 1 reflow e repaint vão realmente acontecer $body.css('padding', '1px'); // reflow, repaint $body.css('color', 'red'); // repaint $body.css('margin', '2px'); // reflow, repaint |
Entretanto, como mencionado acima, acessar uma propriedade de um elemento desencadeia um reflow forçado. Isso acontecerá se se acrescentar uma linha extra que lê uma propriedade do elemento do bloco anterior:
1 2 3 4 5 6 |
var $body = $('body'); $body.css('padding', '1px'); $body.css('padding'); // leitura de propriedade força um reflow $body.css('color', 'red'); $body.css('margin', '2px'); |
Como resultado, obtém-se 2 reflows ao invés de 1. Diante disso, fica claro que é uma boa prática agrupar e fazer leituras de propriedades de elementos em conjunto para otimizar a performance (veja um exemplo mais elucidativo no JSBin).
Há situações em que é preciso forçar um reflow. Exemplo: é preciso aplicar a mesma propriedade (margin-left
, por exemplo) para o mesmo elemento duas vezes. Inicialmente, deve ser definido como 100px
sem animação e, então, tem que ser animado com transição para um valor de 50px
. Você pode estudar este exemplo no JSBin, mas veja a seguir alguns detalhes da implementação.
Começando por criar uma classe CSS com a transição:
1 2 3 4 5 6 |
.has-transition { -webkit-transition: margin-left 1s ease-out; -moz-transition: margin-left 1s ease-out; -o-transition: margin-left 1s ease-out; transition: margin-left 1s ease-out; } |
Procedendo com a implementação:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// elemento tem a classe "has-transition" por padrão var $targetElem = $('#targetElemId'); // remove a classe de transição $targetElem.removeClass('has-transition'); // altera a propriedade esperando acabar com a transição, // já que a classe não está mais ali $targetElem.css('margin-left', 100); // coloca a classe de transição novamente $targetElem.addClass('has-transition'); // altera a propriedade $targetElem.css('margin-left', 50); |
Esta implementação, no entanto, não funciona como esperado. As mudanças são cacheadas e aplicados apenas no fim do bloco de código. É preciso forçar um reflow:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// remove a classe de transição $(this).removeClass('has-transition'); // altera a propriedade $(this).css('margin-left', 100); // força o reflow para as mudanças serem aplicadas imediatamente $(this)[0].offsetHeight; // só um exemplo; também funcionaria com outras propriedades // coloca a classe de transição de volta $(this).addClass('has-transition'); // altera a propriedade $(this).css('margin-left', 50); |
Agora funciona como o esperado!
Conselhos práticos sobre otimização
Em resumo, pode-se aconselhar o seguinte:
- Crie HTML e CSS válidos, não se esqueça de especificar a codificação do documento. Regra geral, estilos devem ser incluídos em
<head>
e scripts externos ao final da tag<body>
. - Simplifique e otimize seletores CSS segundo algumas dicas já mostradas por aqui (essa otimização é quase universalmente ignorada por desenvolvedores que usam pré-processadores CSS).
- Nos scripts, minimize a manipulação do DOM sempre que possível. Faça cache de tudo, incluindo propriedades e objetos (caso estes sejam reutilizados no decorrer do código). É melhor trabalhar com um elemento “offline” ao realizar manipulações complicadas — um elemento “offline” é aquele que está desligado da DOM e só em memória — e anexá-lo ao DOM depois.
- Se você usa jQuery, siga as melhores práticas para seletores jQuery.
- Para alterar os estilos de um elemento, modificar o atributo “class” é uma das maneiras mais performáticas; quanto mais fundo na árvore do DOM for essa mudança, melhor (também pelo fato de que isso ajuda a separar a lógica de apresentação).
- É uma boa ideia desativar animações complexas em
:hover
s durante a rolagem de tela (por exemplo, adicionando uma classe extra “no-hover” a<body>
).
Conclusão
Como é e como se dá o processo de renderização de páginas web deveria ser algo que desenvolvedores front-end aprendem nos primeiros dias de seus estudos — principalmente na medida em que as tecnologias web avançam e o conhecimento dessa parte teórica se torna cada vez mais vital.
Curiosamente, isso não acontece — talvez pelo fato mesmo de os conteúdos a este respeito se encontrarem dispersos e não organizados. Fato é que, com este conhecimento em mãos, é possível planejar e projetar melhor o design de seus códigos e a maneira como isso impactará na performance do site.