“Layout Thrashing” acontece quando o JavaScript lê e escreve no DOM várias vezes, causando reflow (como explicado no artigo sobre renderização de páginas web) — mudanças na renderização do documento que demandam tempo para que o remanejamento de elementos seja calculado.

Quando há escrita no DOM, acontece “invalidação de layout”, e, em algum momento, é preciso que ocorra um refluxo (reflow). O navegador é preguiçoso e quer esperar até o final da operação atual (ou frame) para realizar esse reflow.

Se for requisitado um valor geométrico de retorno do DOM antes da operação atual (ou frame) ser completada, o navegador é forçado a executar o layout antes, o que é conhecido como forced synchronous layout ou layout síncrono forçado, e isso acaba com a performance! Os efeitos colaterais de layout thrashing em navegadores modernos nem sempre são óbvios, mas têm consequências graves, principalmente em dispositivos móveis “low end”.

Solução rápida

Em um mundo ideal, seria possível simplesmente reordenar a execução para fazer a leitura/escrita no DOM de uma só vez (batch), tendo o documento que sofrer reflow somente uma vez.

E no mundo real?

Na realidade, isso não é tão simples, já que aplicações maiores (geralmente) têm códigos espalhados por todo o lugar. Não podemos misturar todo nosso lindo e desacoplado código (e, definitivamente, não deveríamos) somente para ter total controle da ordem de execução; então, o que é possível ser feito em relação às leituras/escritas em batch?

requestAnimationFrame

window.requestAnimationFrame agenda uma função a ser executada no próximo frame, de forma similar a setTimeout(fn, 0). Isso é muito útil porque é possível usá-lo para agendar toda a escrita no DOM para o próximo frame, deixando as leituras para o atual “ciclo” síncrono.

Isso significa que é possível manter o código bem encapsulado e, com um pequeno ajuste, fazer as preciosas operações de acesso ao DOM juntas!

Veja um exemplo funcional que prova o conceito. Na primeira captura de tela, é possível ver um layout thrashing agressivo na timeline. Com as alterações usando requestAnimationFrame, apenas um único evento de layout ocorre e, como resultado, há uma melhora na rapidez de ~96%!

~96% de melhora na rapidez ao usar requestAnimationFrame!

requestAnimationFrame é escalável?

Para exemplos simples que usam requestAnimationFrame é possível adiar escritas no DOM para melhorar a performance, mas esta técnica não é muito escalável. Em um aplicativo “do mundo real”, talvez seja preciso ler a partir do DOM depois de já ter feito a escrita e, então, se está no território do layout thrashing novamente, apenas em um frame diferente.

Também seria possível colocar a leitura em outro requestAnimationFrame, mas não haveria como garantir que outra parte da aplicação não teria programado uma escrita no mesmo frame. Essencialmente, tudo ficaria um pouco caótico e, mais uma vez, não seria possível ter o total controle sobre quando leituras/gravações no DOM aconteceriam…

Como resolver isso?

Apresentando: FastDom

FastDom é uma pequena biblioteca escrita para fornecer uma interface comum para leituras/escritas em lote no DOM. Ela acelera o trabalho no DOM absurdamente utilizando técnicas similares ao requestAnimationFrame descrito acima.

FastDom harmoniza as interações no DOM ao fazer batch de trabalho de leitura e escrita no DOM. Isso significa que é possível construir componentes isolados para aplicação sem se preocupar como eles irão afetar (ou serem afetados) por outros componentes.

Implicações de uso da FastDom

Ao usar FastDom, todas as tarefas no DOM são feitas de forma assíncrona, o que significa que fica muito difícil fazer suposições sobre em que estado estará o DOM. Trabalho que foi feito previamente de forma síncrona pode não ter sido concluído agora, com a assincronia. Para contornar isso, pode-se usar algum sistema de eventos para ser mais explícito sobre quando um trabalho foi finalizado e prover respostas apenas quando se conhece as dependências relacionadas ao DOM.

Também, é preciso ter em mente que pode haver um aumento na quantidade de código a ser escrito para se obter um mesmo resultado. Alguns podem não se importar e considerar isso como um pequeno preço a ser pago para um aumento brutal de performance — por exemplo, eu!

Veja alguns exemplos do FastDom em ação:

Conclusão sobre layout thrashing e FastDom

Atualmente, aplicativos web estão carentes de uma maneira efetiva de resolver o problema de layout thrashing. À medida em que um aplicativo cresce, fica mais difícil coordenar todas suas diferentes partes para garantir um produto final rápido e eficiente. Se FastDom pode ajudar a fornecer uma interface simples para que desenvolvedores possam resolver a questão — mesmo que ao preço de uma pequena curva de aprendizado e uso de uma porcentagem maior de código –, isso só pode significar coisas boas.