Como adicionar interatividade com o JavaScript

Publicado em 31 de dezembro de 2013

O JavaScript nos permite modificar quase todos os aspectos da página: conteúdo, estilo e resposta à interação do usuário. No entanto, o JavaScript também pode bloquear a construção do DOM e atrasar a renderização da página. Para proporcionar um desempenho ideal, faça seu JavaScript assíncrono e elimine qualquer JavaScript desnecessário do caminho crítico de renderização.

  • O JavaScript pode consultar e modificar o DOM e o CSSOM.
  • A execução do JavaScript bloqueia o CSSOM.
  • O JavaScript bloqueia a construção do DOM, a menos que declarado explicitamente como assíncrono.

O JavaScript é uma linguagem dinâmica executada em um navegador que permite alterar praticamente todos os aspectos do comportamento da página. Podemos modificar o conteúdo adicionando e removendo elementos da árvore do DOM; podemos modificar as propriedades do CSSOM de cada elemento; podemos lidar com as interações do usuário; entre muitas outras funções. Para ilustrar isso, confira o que acontece quando o exemplo anterior "Hello World" é alterado para adicionar um script em linha curto:

<!DOCTYPE html>
<html>
 
<head>
   
<meta name="viewport" content="width=device-width,initial-scale=1" />
   
<link href="style.css" rel="stylesheet" />
   
<title>Critical Path: Script</title>
 
</head>
 
<body>
   
<p>Hello <span>web performance</span> students!</p>
   
<div><img src="awesome-photo.jpg" /></div>
   
<script>
     
var span = document.getElementsByTagName('span')[0];
      span
.textContent = 'interactive'; // change DOM text content
      span
.style.display = 'inline'; // change CSSOM property
     
// create a new element, style it, and append it to the DOM
     
var loadTime = document.createElement('div');
      loadTime
.textContent = 'You loaded this page on: ' + new Date();
      loadTime
.style.color = 'blue';
      document
.body.appendChild(loadTime);
   
</script>
 
</body>
</html>

Faça um teste

  • O JavaScript permite acessar o DOM e extrair a referência ao nó de span oculto. O nó pode não estar visível na árvore de renderização, mas está presente no DOM. Depois, quando tivermos a referência, podemos mudar o texto (usando .textContent) e até modificar a propriedade calculada de estilo de exibição de "none" para "inline". Agora nossa página exibe "Hello interactive students!".

  • O JavaScript também permite criar, aplicar estilos, anexar e remover novos elementos no DOM. Tecnicamente, a nossa página inteira poderia ser apenas um grande arquivo de JavaScript que cria e aplica estilos aos elementos, um por um. Embora isso funcione, na prática, usar HTML e CSS é muito mais fácil. Na segunda parte da nossa função JavaScript, criamos um novo elemento div, definimos o conteúdo de texto, aplicamos um estilo e o anexamos ao corpo.

Uma prévia de uma página renderizada em um dispositivo móvel.

Com isso, modificamos o conteúdo e o estilo CSS de um nó DOM existente e adicionamos um nó totalmente novo ao documento. Nossa página não vai ganhar nenhum prêmio de design, mas ilustra o poder e a flexibilidade proporcionados pelo JavaScript.

Porém, embora o JavaScript nos dê muito poder, ele cria muitas limitações adicionais sobre como e quando a página é renderizada.

Primeiro, observe que no exemplo anterior, nosso script inline está perto da parte de baixo da página. Por quê? Bem, você dever tentar fazer isso, mas se mudarmos o script para acima do elemento <span>, ele falha e informa que não consegue encontrar uma referência para qualquer elemento <span> no documento. Ou seja, getElementsByTagName('span') retorna null. Isso demonstra uma propriedade importante: o nosso script é executado no ponto exato em que é inserido no documento. Quando o analisador HTML encontra uma tag de script, ele pausa o processo de construção do DOM e passa o controle para o mecanismo do JavaScript. Depois que o mecanismo do JavaScript conclui a execução, o navegador reinicia do ponto de interrupção e retoma a construção do DOM.

Em outras palavras, nosso bloco de script não consegue encontrar elementos definidos posteriormente na página porque ainda não foram processados. Ou, de forma ligeiramente diferente, a execução do nosso script inline bloqueia a construção do DOM, o que, por sua vez, também retarda a renderização inicial.

Outra propriedade sutil da inclusão de scripts em nossa página é que, além de ler e modificar o DOM, eles também podem fazer o mesmo nas propriedades do CSSOM. Na verdade, é exatamente isso que fazemos no nosso exemplo quando mudamos a propriedade de exibição do elemento span de none para inline. O resultado final? Criamos uma condição de corrida.

E se o navegador não tiver concluído o download e a criação do CSSOM no momento da execução do script? A resposta não é muito boa para a performance: o navegador interrompe a execução do script e a construção do DOM até concluir o download e a construção do CSSOM.

Em suma, o JavaScript introduz uma série de novas dependências entre o DOM, o CSSOM e a execução de JavaScript. Isso pode causar atrasos significativos no navegador ao processar e renderizar a página na tela:

  • O local do script no documento é significativo.
  • Quando o navegador encontra uma tag de script, a construção do DOM faz uma pausa até que a execução do script termine.
  • O JavaScript pode consultar e modificar o DOM e o CSSOM.
  • A execução do JavaScript faz uma pausa até que o CSSOM esteja pronto.

Em grande medida, "otimizar o caminho crítico de renderização" se refere a compreender e otimizar o gráfico de dependência entre HTML, CSS e JavaScript.

Bloqueio de analisador versus JavaScript assíncrono

Por padrão, a execução do JavaScript bloqueia o analisador. Quando o navegador encontra um script no documento, deve suspender a construção do DOM, passar o controle ao tempo de execução do JavaScript e deixar que o script seja executado antes de continuar com a construção do DOM. Vimos como isso funciona com um script em linha no exemplo anterior. Na verdade, scripts inline sempre bloqueiam o analisador, a menos que você crie código adicional para adiar a execução dos scripts.

E os scripts incluídos com uma tag de script? No exemplo anterior, extraia o código para um arquivo separado:

<!DOCTYPE html>
<html>
 
<head>
   
<meta name="viewport" content="width=device-width,initial-scale=1" />
   
<link href="style.css" rel="stylesheet" />
   
<title>Critical Path: Script External</title>
 
</head>
 
<body>
   
<p>Hello <span>web performance</span> students!</p>
   
<div><img src="awesome-photo.jpg" /></div>
   
<script src="app.js"></script>
 
</body>
</html>

app.js

var span = document.getElementsByTagName('span')[0];
span
.textContent = 'interactive'; // change DOM text content
span
.style.display = 'inline'; // change CSSOM property
// create a new element, style it, and append it to the DOM
var loadTime = document.createElement('div');
loadTime
.textContent = 'You loaded this page on: ' + new Date();
loadTime
.style.color = 'blue';
document
.body.appendChild(loadTime);

Faça um teste

Quer usemos uma tag <script> ou um snippet de JavaScript inline, espera-se que ambos se comportem da mesma maneira. Em ambos os casos, o navegador pausa e executa o script antes de processar o restante do documento. No entanto, no caso de um arquivo JavaScript externo, o navegador precisa pausar para esperar que o script seja buscado do disco, do cache ou de um servidor remoto, o que pode adicionar dezenas a milhares de milissegundos de atraso ao caminho crítico de renderização.

Por padrão, todo JavaScript bloqueia o analisador. Como o navegador não sabe o que o script está planejando fazer na página, ele assume o pior cenário e bloqueia o analisador. Um sinal para o navegador de que o script não precisa ser executado no ponto exato onde é referenciado permite que o navegador continue construindo o DOM e deixe o script ser executado quando estiver pronto; por exemplo, depois que o arquivo é recuperado a partir do cache ou de um servidor remoto.

Para fazer isso, o atributo async é adicionado ao elemento <script>:

<!DOCTYPE html>
<html>
 
<head>
   
<meta name="viewport" content="width=device-width,initial-scale=1" />
   
<link href="style.css" rel="stylesheet" />
   
<title>Critical Path: Script Async</title>
 
</head>
 
<body>
   
<p>Hello <span>web performance</span> students!</p>
   
<div><img src="awesome-photo.jpg" /></div>
   
<script src="app.js" async></script>
 
</body>
</html>

Faça um teste

Adicionar a palavra-chave "async" à tag de script informa ao navegador para não bloquear a construção do DOM enquanto aguarda que o script seja disponibilizado, o que pode melhorar significativamente o desempenho.

Feedback