Práticas recomendadas de carregamento lento

Embora o carregamento lento de imagens e vídeos tenha benefícios positivos e mensuráveis para a performance, essa não é uma tarefa simples. Se você cometer um erro, poderá haver consequências indesejadas. Por isso, é importante ter em mente as seguintes preocupações.

Lembre-se da dobra

Pode ser tentador fazer o carregamento lento de todos os recursos de mídia da página com JavaScript, mas você precisa resistir a essa tentação. Os itens acima da dobra não podem ser carregados lentamente. Esses recursos precisam ser considerados essenciais e, portanto, devem ser carregados normalmente.

O carregamento lento atrasa o carregamento de recursos até que o DOM fique interativo, quando os scripts terminam de carregar e iniciar a execução. Para imagens abaixo da dobra, tudo bem, mas os recursos essenciais acima da dobra precisam ser carregados com o elemento <img> padrão para que sejam exibidos o mais rápido possível.

É claro que a posição da dobra não está tão clara hoje em dia, quando os sites são visualizados em tantas telas de tamanhos variados. O que está acima da dobra em um laptop pode muito bem estar abaixo dela em dispositivos móveis. Não há uma fórmula mágica para abordar isso de maneira ideal em todas as situações. Você precisará fazer um inventário dos recursos essenciais da sua página e carregar essas imagens da maneira típica.

Além disso, talvez você não queira ser tão rigoroso com a linha da dobra em relação ao limite para acionar o carregamento lento. Pode ser mais adequado estabelecer uma zona de buffer um pouco abaixo da dobra para que as imagens comecem a ser carregadas bem antes de o usuário rolar a tela até a janela de visualização. Por exemplo, a API Intersection Observer permite especificar uma propriedade rootMargin em um objeto de opções ao criar uma nova instância IntersectionObserver. Isso efetivamente fornece um buffer aos elementos, que aciona o comportamento de carregamento lento antes que o elemento esteja na janela de visualização:

let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
  // lazy-loading image code goes here
}, {
  rootMargin: "0px 0px 256px 0px"
});

Se o valor de rootMargin for semelhante aos valores que você especificaria para uma propriedade margin do CSS, isso significa que ele é. Nesse caso, a margem inferior do elemento observado (a janela de visualização do navegador por padrão, mas isso pode ser alterada para um elemento específico usando a propriedade root) é ampliada em 256 pixels. Isso significa que a função de callback será executada quando um elemento de imagem estiver dentro de 256 pixels da janela de visualização, e a imagem começará a ser carregada antes que o usuário a veja.

Para ter esse mesmo efeito em navegadores sem suporte ao Intersection Observe, use o código de processamento de eventos de rolagem e ajuste a verificação getBoundingClientRect para incluir um buffer.

Mudança de layout e marcadores de posição

O carregamento lento de mídia pode causar mudança no layout se marcadores não forem usados. Essas mudanças podem ser desorientadoras para os usuários e acionam operações dispendiosas de layout DOM que consomem recursos do sistema e contribuem para a instabilidade. No mínimo, considere usar um marcador de posição de cor sólida que ocupe as mesmas dimensões que a imagem de destino ou técnicas como LQIP ou SQIP, que sugerem o conteúdo de um item de mídia antes que ele seja carregado.

Em tags <img>, src precisa apontar inicialmente para um marcador até que esse atributo seja atualizado com o URL da imagem final. Use o atributo poster em um elemento <video> para apontar para uma imagem de marcador. Além disso, use os atributos width e height nas tags <img> e <video>. Isso garante que a transição de marcadores de posição para imagens finais não mude o tamanho renderizado do elemento conforme a mídia é carregada.

Atrasos na decodificação de imagens

Carregar imagens grandes em JavaScript e soltá-las no DOM pode vincular a linha de execução principal, fazendo com que a interface do usuário não responda por um curto período durante a decodificação. A decodificação assíncrona de imagens usando o método decode antes de inseri-las no DOM pode reduzir esse tipo de instabilidade, mas tenha cuidado: ele ainda não está disponível em todos os lugares e aumenta a complexidade da lógica de carregamento lento. Se quiser usá-lo, será necessário verificar. Confira abaixo como usar Image.decode() com um substituto:

var newImage = new Image();
newImage.src = "my-awesome-image.jpg";

if ("decode" in newImage) {
  // Fancy decoding logic
  newImage.decode().then(function() {
    imageContainer.appendChild(newImage);
  });
} else {
  // Regular image load
  imageContainer.appendChild(newImage);
}

Confira este link do CodePen para ver um código semelhante a este exemplo em ação. Se a maioria das imagens for muito pequena, isso não vai lhe ajudar muito, mas certamente ajudará a reduzir a instabilidade ao carregar imagens grandes lentamente e inseri-las no DOM.

Quando o carregamento não carrega

Às vezes, os recursos de mídia não são carregados por algum motivo ou outro, e erros ocorrem. Quando isso pode acontecer? Depende, mas este é um cenário hipotético para você: você tem uma política de armazenamento em cache HTML por um curto período (por exemplo, cinco minutos), e o usuário visita o site ou deixa uma guia desatualizada aberta por um longo período (por exemplo, várias horas) e volta para ler seu conteúdo. Em algum momento desse processo, ocorre uma reimplantação. Durante essa implantação, o nome de um recurso de imagem é alterado devido ao controle de versão baseado em hash ou é totalmente removido. Quando o usuário faz o carregamento lento da imagem, o recurso fica indisponível e, portanto, falha.

Embora essas sejam ocorrências relativamente raras, pode ser útil que você tenha um plano de backup se o carregamento lento falhar. Para imagens, essa solução pode ter esta aparência:

var newImage = new Image();
newImage.src = "my-awesome-image.jpg";

newImage.onerror = function(){
  // Decide what to do on error
};
newImage.onload = function(){
  // Load the image
};

O que você decide fazer em caso de erro depende do aplicativo. Por exemplo, você pode substituir a área do marcador de posição da imagem por um botão que permita que o usuário tente carregar a imagem novamente ou simplesmente mostre uma mensagem de erro na área do marcador.

Outros cenários também podem surgir. O que quer que você faça, é sempre uma boa ideia informar ao usuário quando ocorreu um erro e possivelmente sugerir uma ação a ser tomada se algo der errado.

Disponibilidade do JavaScript

Não presuma que o JavaScript está sempre disponível. Se você fizer o carregamento lento de imagens, ofereça a marcação <noscript>, que vai mostrar imagens caso o JavaScript não esteja disponível. O exemplo substituto mais simples possível envolve o uso de elementos <noscript> para exibir imagens se o JavaScript estiver desativado:

Sou uma imagem!

Se o JavaScript estiver desativado, os usuários verão a imagem do marcador e a imagem contida com os elementos <noscript>. Para contornar isso, coloque uma classe de no-js na tag <html> da seguinte forma:

<html class="no-js">

Em seguida, coloque uma linha de script in-line no <head> antes que as folhas de estilo sejam solicitadas pelas tags <link>. Elas removem a classe no-js do elemento <html> se o JavaScript estiver ativado:

<script>document.documentElement.classList.remove("no-js");</script>

Por fim, use CSS para ocultar elementos com uma classe de lentidão quando o JavaScript não estiver disponível:

.no-js .lazy {
  display: none;
}

Isso não impede o carregamento de imagens de marcador de posição, mas o resultado é mais desejável. As pessoas com o JavaScript desativado recebem algo além de imagens de marcador, o que é melhor do que marcadores de posição e conteúdo de imagem sem conteúdo significativo.