Evite layouts grandes e complexos e a troca frequente de layouts

Publicado em 20 de março de 2015, última atualização: 7 de maio de 2025

O layout é onde o navegador descobre as informações geométricas dos elementos: o tamanho e a localização deles na página. Cada elemento terá informações de dimensionamento explícitas ou implícitas com base no CSS usado, no conteúdo do elemento ou em um elemento pai. O processo é chamado de layout no Chrome (e navegadores derivados, como o Edge) e no Safari. No Firefox, ele é chamado de "reflow", mas o processo é o mesmo.

Assim como nos cálculos de estilo, as preocupações imediatas com o custo do layout são:

  1. O número de elementos que exigem layout, que é um subproduto do tamanho do DOM da página.
  2. A complexidade desses layouts.

Resumo

  • O layout tem efeito direto na latência de interação
  • O layout normalmente é aplicado a todo o documento.
  • O número de elementos DOM afeta a performance. Evite acionar o layout sempre que possível.
  • Evite layouts síncronos forçados e o uso excessivo de layout. Leia os valores do estilo e faça mudanças.

Os efeitos do layout na latência de interação

Quando um usuário interage com a página, essas interações precisam ser o mais rápidas possível. O tempo necessário para concluir uma interação, que termina quando o navegador apresenta o próximo frame para mostrar os resultados da interação, é conhecido como latência de interação. Esse é um aspecto da performance da página que a métrica Interaction to Next Paint mede.

O tempo que o navegador leva para apresentar o próximo frame em resposta a uma interação do usuário é conhecido como delay de apresentação da interação. O objetivo de uma interação é fornecer feedback visual para sinalizar ao usuário que algo ocorreu, e as atualizações visuais podem envolver algum trabalho de layout para alcançar essa meta.

Para manter o INP do seu site o mais baixo possível, é importante evitar o layout sempre que possível. Se não for possível evitar o layout totalmente, é importante limitar o trabalho de layout para que o navegador possa apresentar o próximo frame rapidamente.

Evite o layout sempre que possível

Quando você muda os estilos, o navegador verifica se alguma das mudanças exige que o layout seja calculado e se a árvore de renderização precisa ser atualizada. As mudanças nas "propriedades geométricas", como width, height, left ou top, exigem layout.

.box {
  width: 20px;
  height: 20px;
}

/**
  * Changing width and height
  * triggers layout.
  */

.box--expanded {
  width: 200px;
  height: 350px;
}

O layout quase sempre tem o escopo de todo o documento. Se você tiver muitos elementos, vai levar muito tempo para descobrir os locais e as dimensões de todos eles.

Se não for possível evitar o layout, a chave é usar novamente o Chrome DevTools para saber quanto tempo está demorando e determinar se o layout é a causa de um gargalo. Primeiro, abra o DevTools, acesse a guia "Timeline", clique em "Record" e interaja com o site. Quando você parar de gravar, vai aparecer um detalhamento do desempenho do seu site:

As Ferramentas do desenvolvedor mostram vários blocos de layout roxos.
Chrome DevTools mostrando um longo tempo no Layout.

Ao analisar o rastro no exemplo anterior, vemos que mais de 28 milissegundos são gastos no layout para cada frame, o que, quando temos 16 milissegundos para exibir um frame na tela em uma animação, é muito alto. Você também pode ver que as Ferramentas do desenvolvedor informam o tamanho da árvore (1.618 elementos neste caso) e quantos nós precisavam de layout (5 neste caso).

O conselho geral aqui é evitar o layout sempre que possível, mas nem sempre é possível evitar o layout. Nos casos em que não é possível evitar o layout, saiba que o custo dele tem uma relação com o tamanho do DOM. Embora a relação entre os dois não seja muito estreita, DOMs maiores geralmente incorrem em custos de layout mais altos.

Evitar layouts síncronos forçados

O envio de um frame para a tela tem esta ordem:

Usando o flexbox como layout.
Etapas de renderização

Primeiro, o JavaScript é executado, depois os cálculos de estilo e, depois o layout. No entanto, é possível forçar um navegador a executar o layout mais cedo com JavaScript. Isso é chamado de layout síncrono forçado (ou às vezes refluxo forçado).

A primeira coisa a se lembrar é que, à medida que o JavaScript é executado, todos os valores de layout antigos do frame anterior são conhecidos e disponíveis para consulta. Por exemplo, se você quiser escrever a altura de um elemento (vamos chamar de "caixa") no início do frame, escreva um código como este:

// Schedule our function to run at the start of the frame:
requestAnimationFrame(logBoxHeight);

function logBoxHeight () {
  // Gets the height of the box in pixels and logs it out:
  console.log(box.offsetHeight);
}

As coisas ficam problemáticas se você tiver mudado os estilos da caixa antes de solicitar a altura dela:

function logBoxHeight () {
  box.classList.add('super-big');

  // Gets the height of the box in pixels and logs it out:
  console.log(box.offsetHeight);
}

Agora, para responder à pergunta sobre a altura, o navegador precisa primeiro aplicar a mudança de estilo (por causa da adição da classe super-big) e depois executar o layout. Só então ele poderá retornar a altura correta. Isso é desnecessário e pode ser caro.

Por isso, sempre faça a leitura em lote dos estilos e faça isso primeiro (quando o navegador pode usar os valores de layout do frame anterior) e depois faça as gravações:

Uma versão mais eficiente da função anterior seria:

function logBoxHeight () {
  // Gets the height of the box in pixels and logs it out:
  console.log(box.offsetHeight);

  box.classList.add('super-big');
}

Na maioria das vezes, não é necessário aplicar estilos e consultar valores. Usar os valores do último frame é suficiente. Executar os cálculos de estilo e layout de forma síncrona e antes do que o navegador gostaria são possíveis gargalos, e não algo que você normalmente quer fazer.

Evite a sobrecarga de layout

Há uma maneira de tornar os layouts síncronos forçados ainda piores: fazer muitos deles em sucessão rápida. Confira este código:

function resizeAllParagraphsToMatchBlockWidth () {
  // Puts the browser into a read-write-read-write cycle.
  for (let i = 0; i < paragraphs.length; i++) {
    paragraphs[i].style.width = `${box.offsetWidth}px`;
  }
}

Esse código repete um grupo de parágrafos e define a largura de cada parágrafo para corresponder à largura de um elemento chamado "caixa". Parece inofensivo, mas o problema é que cada iteração do loop lê um valor de estilo (box.offsetWidth) e o usa imediatamente para atualizar a largura de um parágrafo (paragraphs[i].style.width). Na próxima iteração do loop, o navegador precisa considerar o fato de que os estilos mudaram desde a última solicitação de offsetWidth (na iteração anterior), portanto, ele precisa aplicar as mudanças de estilo e executar o layout. Isso vai acontecer em cada iteração.

A correção para este exemplo é ler e gravar os valores novamente:

// Read.
const width = box.offsetWidth;

function resizeAllParagraphsToMatchBlockWidth () {
  for (let i = 0; i < paragraphs.length; i++) {
    // Now write.
    paragraphs[i].style.width = `${width}px`;
  }
}

Identificar layouts síncronos forçados e thrashing

As DevTools têm um insight de reflow forçado para ajudar a identificar rapidamente casos de layouts síncronos forçados (também conhecidos como "reflow forçado"):

As ferramentas do desenvolvedor mostram um insight de reflow forçado que identifica uma função chamada &quot;w&quot;, que está causando o reflow forçado.
Insights de reflow forçado do Chrome DevTools.

Os layouts síncronos forçados também podem ser identificados no campo usando a atribuição de script da API Long Animation Frame com a propriedade forcedStyleAndLayoutDuration.