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ê errar, poderá haver consequências não intencionais. Por isso, é importante ter em mente as preocupações a seguir.

Cuidado com a 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. Qualquer item acima da dobra não deve ser carregado lentamente. Esses recursos precisam ser considerados críticos e, portanto, precisam ser carregados normalmente.

O carregamento lento atrasa os recursos até que o DOM se torne interativo, quando os scripts terminarem de carregar e iniciarem 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.

O local da dobra não é tão claro hoje em dia, quando os sites são visualizados em muitas 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 lidar com isso da melhor forma em todas as situações. Você precisa fazer um inventário dos recursos essenciais da sua página e carregar essas imagens normalmente.

Além disso, talvez você não queira ser tão rigoroso com a linha da dobra quanto ao limite para acionar o carregamento lento. Pode ser mais ideal que seu objetivo seja estabelecer uma zona de buffer um pouco abaixo da dobra para que as imagens comecem a carregar bem antes do 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 fornece efetivamente aos elementos um buffer, que aciona um 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, é porque ele é. Nesse caso, a margem de baixo do elemento observado (a janela de visualização do navegador por padrão, mas isso pode ser mudado 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 a até 256 pixels da janela de visualização, e a imagem vai começar a carregar antes que o usuário a veja.

Para conseguir esse mesmo efeito em navegadores que não têm suporte ao Intersection Observe, use o código de processamento de eventos de rolagem e ajuste a marcaçã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 os marcadores de posição não forem usados. Essas mudanças podem desorientar os usuários e acionar operações dispendiosas de layout DOM que consomem recursos do sistema e contribuem para a instabilidade. No mínimo, use um marcador de cor sólida que ocupe as mesmas dimensões da imagem de destino ou técnicas como LQIP ou SQIP que indiquem o conteúdo de um item de mídia antes que ele seja carregado.

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

Atrasos na decodificação de imagem

Carregar imagens grandes em JavaScript e soltá-las no DOM pode amarrar a linha de execução principal, fazendo com que a interface do usuário pare de responder por um curto período durante a decodificação. A decodificação de imagens assíncrona usando o método decode antes de inseri-las no DOM pode reduzir esse tipo de instabilidade, mas tenha cuidado: ela ainda não está disponível em todos os lugares e aumenta a complexidade da lógica de carregamento lento. Se você quiser usá-lo, será necessário verificá-lo. Veja abaixo como você pode 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 ao exemplo em ação. Se a maioria das imagens for bastante pequena, isso poderá não ser muito útil, mas certamente poderá ajudar a reduzir a instabilidade ao carregar imagens grandes e inseri-las no DOM lentamente.

Quando o carregamento não é concluído

Às vezes, os recursos de mídia não carregam por algum motivo e ocorrem erros. Quando isso pode acontecer? Depende, mas há 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 o conteúdo. Em algum momento desse processo, ocorre uma reimplantação. Durante essa implantação, o nome de um recurso de imagem muda devido ao controle de versões baseado em hash ou é completamente removido. Quando o usuário faz o carregamento lento da imagem, o recurso fica indisponível e falha.

Essas são ocorrências relativamente raras, mas é importante que você tenha um plano de backup caso o carregamento lento falhe. 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 seu aplicativo. Por exemplo, é possível substituir a área do marcador da imagem por um botão que permita ao usuário tentar carregar a imagem novamente ou simplesmente mostrar uma mensagem de erro na área do marcador.

Outros cenários também podem surgir. O que você fizer, nunca é uma má ideia informar ao usuário quando um erro ocorreu e possivelmente dar a ele uma ação se algo der errado.

Disponibilidade do JavaScript

Não pense que o JavaScript está sempre disponível. Se você for usar imagens de carregamento lento, 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 vão ver tanto a imagem do marcador quanto a imagem contida nos elementos <noscript>. Para contornar isso, coloque uma classe de no-js na tag <html> da seguinte maneira:

<html class="no-js">

Em seguida, coloque uma linha de script in-line no <head> antes que qualquer folha de estilo seja solicitada por tags <link> que removam a classe no-js do elemento <html> se o JavaScript estiver ativado:

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

Por fim, use um pouco de 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 que as imagens de marcador de posição sejam carregadas, mas o resultado é mais desejável. As pessoas com o JavaScript desativado recebem algo mais do que imagens de marcador, o que é melhor do que marcadores de posição e nenhum conteúdo de imagem significativo.