Como reduzi meu LCP em 70%
Aprenda métodos avançados para melhorar os Core Web Vitals

Melhorando as métricas de LCP com web workers e carregamento de imagem em 2 etapas
Na maioria das vezes, um elemento de imagem grande na viewport visível se tornará o elemento Largest Contentful Paint. Mesmo depois de aplicar todas as melhores práticas do Lighthouse, como redimensionamento de imagens, compressão de imagens, conversão para WebP e preload do elemento LCP, seu Largest Contentful Paint ainda pode não passar nos Core Web Vitals.
A única maneira de corrigir isso é usando táticas mais avançadas como carregamento em 2 etapas e threading da sua página com web workers para liberar recursos na thread principal.
Neste artigo, mostrarei como melhorar ainda mais o Largest Contentful Paint.

Um pouco de contexto
Eu sou um cara de pagespeed e meu site é minha vitrine. Na minha homepage, orgulhosamente afirmo que meu site é o site mais rápido do mundo. É por isso que preciso que minha página carregue o mais rápido possível e extraia cada gota de pagespeed do meu site.
As técnicas que mostrarei hoje podem não ser viáveis para o seu site médio (WordPress) sem o suporte de uma equipe de desenvolvimento dedicada e talentosa. Se você não conseguir duplicar essa técnica em seu próprio site, ainda assim encorajo você a ler o artigo e aprender como eu penso sobre pagespeed e quais são minhas considerações.
O problema: imagens grandes na viewport visível
Uma imagem grande na viewport visível frequentemente se tornará o elemento Largest Contentful Paint. É comum que essa imagem LCP não passe nos Core Web Vitals. Eu vejo resultados como este diariamente.

Existem várias maneiras de garantir que este elemento apareça na tela rapidamente:
- Faça preload do elemento LCP. Fazer preload da imagem LCP garantirá que esta imagem esteja disponível para o navegador o mais cedo possível.
- Use imagens responsivas. Certifique-se de que não está servindo imagens de tamanho desktop para dispositivos móveis.
- Comprima suas imagens. A compressão de imagens pode reduzir drasticamente o tamanho da imagem
- Use formatos de imagem de nova geração. Formatos de imagem de nova geração como WebP superam formatos mais antigos como JPEG e PNG em quase todos os casos.
- Minimize o caminho crítico de renderização. Elimine todos os recursos que bloqueiam a renderização, como JavaScripts e folhas de estilo, que possam atrasar o LCP.
Infelizmente, apesar de todas essas otimizações, em alguns casos, as métricas de LCP ainda podem não passar na auditoria dos Core Web Vitals. Por quê? O tamanho da imagem sozinho é suficiente para atrasar o LCP.
A solução: carregamento em 2 etapas e web workers
A solução que implementei (depois de otimizar todos os outros problemas do meu site) é o carregamento de imagem em 2 etapas.
A ideia é simples: na primeira renderização, mostre uma imagem de baixa qualidade com as mesmas dimensões exatas da imagem final de alta qualidade. Imediatamente após essa imagem ser exibida, inicie o processo que troca a imagem de baixa qualidade pela imagem de alta qualidade.
Uma implementação muito básica pode parecer algo assim: Primeiro, adicione um event listener de load a uma imagem. Quando a imagem carrega, esse mesmo event listener se desanexa e o src da imagem é trocado pela imagem final de alta qualidade.
<img
width="100"
height="100"
alt="algum texto alternativo"
src="lq.webp"
onload="this.onload=null;this.src='hq.webp'"
> Etapa 1: webp de baixa qualidade 3-5kb 
Etapa 2: webp de alta qualidade 20-40kb 
Isso pode parecer simples o suficiente (e é), mas trocar um grande número de imagens no início do processo de renderização causará muita atividade na thread principal e afetará outras métricas de Core Web Vitals.
É por isso que escolhi descarregar parte do trabalho para um web worker. Um web worker roda em uma nova thread e não tem acesso real à página atual. A comunicação entre o web worker e a página é feita através de um sistema de mensagens. A vantagem óbvia é que não estamos usando a thread principal da página em si, estamos liberando recursos lá. A desvantagem é que usar um web worker pode ser um pouco trabalhoso.
O processo em si não é tão difícil. Assim que o evento DomContentLoaded é disparado, coleto todas as imagens da página. Se uma imagem já foi carregada, farei a troca imediatamente. Se não foi carregada (porque a imagem pode fazer lazy load), anexarei um event listener que troca a imagem após o lazy load.
O resultado: espetacular

O código para carregamento de LCP em 2 etapas através de um web worker
Aqui está o código que uso para acelerar meu LCP através de carregamento em 2 etapas e um web worker. O código na página principal chama um web worker que buscará as imagens. O web worker passa o resultado como um blob para a página principal. Ao receber o blob, a imagem é trocada.
Worker.js
self.addEventListener('message', async event => {
const newimageURL = event.data.src.replace("/lq-","/resize-");
const response = await fetch(newimageURL)
const blob = await response.blob()
// Send the image data to the UI thread!
self.postMessage({
uid: event.data.uid,
blob: blob,
})
}) Script.js
O script.js roda como um script normal na página ativa. O script primeiro carrega o worker. Em seguida, percorre todas as imagens da página. Isso acontece no início do processo de renderização. Uma imagem pode já estar carregada ou não. Se uma imagem de baixa qualidade já estiver carregada, chamará o processo de troca imediatamente. Se ainda não estiver carregada, anexará um listener ao evento de load da imagem que inicia o processo de troca assim que a imagem for carregada.
Quando uma imagem é carregada, um id único é gerado para ela. Isso me permite encontrar facilmente a imagem na página novamente (lembre-se, o worker não tem acesso ao DOM, então não posso enviar o nó DOM da imagem).
A URL da imagem e o id único são então enviados ao worker.
Quando o worker busca a imagem, ela é enviada de volta ao script como um blob. O script eventualmente troca a URL antiga da imagem pela URL do blob que foi criado pelo web worker.
var myWorker = new Worker('/path-to/worker.js');
// send a message to worker
const sendMessage = (img) => {
// uid makes it easier to find the image
var uid = create_UID();
// set data-uid on image element
img.dataset.uid = uid;
// send message to worker
myWorker.postMessage({ src: img.src, uid: uid });
};
// generate the uid
const create_UID = () => {
var dt = new Date().getTime();
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = (new Date().getTime() + Math.random() * 16) % 16 | 0;
dt = Math.floor(dt / 16);
return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16);
});
return uid;
}
// when we get a result from the worker
myWorker.addEventListener('message', event => {
// Grab the message data from the event
const imageData = event.data
// Get the original element for this image
const imageElement = document.querySelectorAll("img[data-uid='" + imageData.uid + "']");
// We can use the `Blob` as an image source! We just need to convert it
// to an object URL first
const objectURL = URL.createObjectURL(imageData.blob)
// Once the image is loaded, we'll want to do some extra cleanup
imageElement.onload = () => {
URL.revokeObjectURL(objectURL)
}
imageElement[0].setAttribute('src', objectURL)
})
// get all images
document.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll('img[loading="lazy"]').forEach(
img => {
// image is already visible?
img.complete ?
// swap immediately
sendMessage(img) :
// swap on load
img.addEventListener(
"load", i => { sendMessage(img) }, { once: true }
)
})
}) Pontuação de Core Web Vitals com imagem LCP pré-carregada
Make decisions with Data.
You cannot optimize what you do not measure. Install the CoreDash pixel and capture 100% of user experiences.
- 100% Capture
- Data Driven
- Easy Install

