Kuinka laskin LCP:n 70 %
Opi edistyneitä menetelmiä Core Web Vitals -tulosten parantamiseen

LCP-mittareiden parantaminen web workereilla ja 2-vaiheisella kuvalatauksella
Useimmiten suuri kuvaelementti näkyvässä näkymäikkunassa tulee Largest Contentful Paint -elementiksi. Vaikka soveltaisit kaikki Lighthouse-parhaat käytännöt, kuten kuvien koon muuttamisen, kuvien pakkaamisen, WebP-muunnoksen ja LCP-elementin esilataamisen, Largest Contentful Paint ei silti välttämättä läpäise Core Web Vitals -tarkistusta.
Ainoa tapa korjata tämä on käyttää edistyneempiä taktiikoita, kuten 2-vaiheista latausta ja sivun säikeistämistä web workereilla vapauttaaksesi resursseja pääsäikeellä.
Tässä artikkelissa näytän, kuinka Largest Contentful Paint -arvoa voidaan edelleen parantaa.

Taustatietoa
Olen sivunopeuden asiantuntija ja verkkosivustoni on näyteikkunani. Etusivullani väitän ylpeästi sivustoni olevan maailman nopein. Siksi sivuni on ladattava mahdollisimman nopeasti ja jokaisesta sivunopeuden pisarasta on puristettava kaikki irti.
Tekniikat, joita näytän tänään, eivät välttämättä sovellu tavalliselle (WordPress-)sivustolle ilman omistautuneen ja taitavan kehitystiimin tukea. Vaikka et pystyisi toteuttamaan tätä tekniikkaa omalla sivustollasi, kannustan silti lukemaan artikkelin ja oppimaan, miten ajattelen sivunopeudesta ja mitä huomioon otettavia seikkoja minulla on.
Ongelma: suuret kuvat näkyvässä näkymäikkunassa
Suuri kuva näkyvässä näkymäikkunassa tulee usein Largest Contentful Paint -elementiksi. Usein käy niin, ettei tämä LCP-kuva läpäise Core Web Vitals -tarkistusta. Näen tällaisia tuloksia päivittäin.

On useita tapoja varmistaa, että tämä elementti ilmestyy näytölle nopeasti:
- Esilataa LCP-elementti. LCP-kuvan esilataaminen varmistaa, että kuva on selaimen käytettävissä mahdollisimman aikaisin.
- Käytä responsiivisia kuvia. Varmista, ettet tarjoa työpöytäkokoisia kuvia mobiililaitteille.
- Pakkaa kuvasi. Kuvien pakkaaminen voi vähentää kuvan kokoa merkittävästi.
- Käytä seuraavan sukupolven kuvaformaatteja. Seuraavan sukupolven kuvaformaatit, kuten WebP, päihittävät vanhemmat formaatit, kuten JPEG ja PNG, lähes kaikissa tapauksissa.
- Minimoi kriittinen renderöintipolku. Poista kaikki renderöintiä estävät resurssit, kuten JavaScript-tiedostot ja tyylitiedostot, jotka saattavat viivästyttää LCP:tä.
Valitettavasti kaikista näistä optimoinneista huolimatta LCP-mittarit eivät joissain tapauksissa silti läpäise Core Web Vitals -tarkistusta. Miksi? Kuvan koko yksinään riittää viivästyttämään LCP:tä.
Ratkaisu: 2-vaiheinen lataus ja web workerit
Ratkaisu, jonka toteutin (optimoituani kaikki muut sivustoni ongelmat), on 2-vaiheinen kuvien lataus.
Idea on yksinkertainen: ensimmäisellä renderöinnillä näytetään heikkolaatuinen kuva, jolla on täsmälleen samat mitat kuin lopullisella korkealaatuisella kuvalla. Heti kun tuo kuva on näytetty, käynnistetään prosessi, joka vaihtaa heikkolaatuisen kuvan korkealaatuiseen kuvaan.
Hyvin yksinkertainen toteutus voisi näyttää tältä: Lisää ensin lataustapahtuman kuuntelija kuvaan. Kun kuva latautuu, sama tapahtumakuuntelija irrottaa itsensä ja kuvan src vaihdetaan lopulliseen, korkealaatuiseen kuvaan.
<img
width="100"
height="100"
alt="some alt text"
src="lq.webp"
onload="this.onload=null;this.src='hq.webp'"
> Vaihe 1: heikkolaatuinen webp 3–5 kt 
Vaihe 2: korkealaatuinen webp 20–40 kt 
Tämä saattaa vaikuttaa riittävän yksinkertaiselta (ja onkin), mutta suuren kuvamäärän vaihtaminen varhain renderöintiprosessissa aiheuttaa liikaa toimintaa pääsäikeellä ja vaikuttaa muihin Core Web Vitals -mittareihin.
Siksi päätin siirtää osan työstä web workerille. Web worker toimii uudessa säikeessä eikä sillä ole todellista pääsyä nykyiselle sivulle. Viestintä web workerin ja sivun välillä tapahtuu viestijärjestelmän kautta. Ilmeinen etu on, ettei sivun pääsäiettä itsessään käytetä, vaan vapautamme siellä resursseja. Haittana on, että web workerin käyttö voi olla hieman hankalaa.
Itse prosessi ei ole kovin vaikea. Kun DomContentLoaded-tapahtuma on käynnistynyt, kerään kaikki sivun kuvat. Jos kuva on jo ladattu, vaihdan sen välittömästi. Jos sitä ei ole vielä ladattu (koska kuva saattaa latautua laiskasti), liitän tapahtumakuuntelijan, joka vaihtaa kuvan laiskan latauksen jälkeen.
Tulos: vaikuttava

Koodi 2-vaiheiseen LCP-lataukseen web workerin kautta
Tässä on koodi, jota käytän LCP:n nopeuttamiseen 2-vaiheisella latauksella ja web workerilla. Pääsivun koodi kutsuu web workeria, joka hakee kuvat. Web worker välittää tuloksen blobina pääsivulle. Blobin vastaanottamisen jälkeen kuva vaihdetaan.
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
Script.js suoritetaan normaalina skriptinä aktiivisella verkkosivulla. Skripti lataa ensin workerin. Sitten se käy läpi kaikki sivun kuvat. Tämä tapahtuu varhain renderöintiprosessissa. Kuva saattaa olla jo ladattu tai ei. Jos heikkolaatuinen kuva on jo ladattu, se kutsuu vaihtoprosessia välittömästi. Jos sitä ei ole vielä ladattu, se liittää kuuntelijan kuvan lataustapahtumaan, joka käynnistää vaihtoprosessin heti kun kuva on ladattu.
Kun kuva on ladattu, sille luodaan yksilöllinen tunniste. Tämä mahdollistaa kuvan löytämisen sivulta helposti uudelleen (muista, workerilla ei ole pääsyä DOM:iin, joten en voi lähettää kuvan DOM-solmua).
Kuvan URL ja yksilöllinen tunniste lähetetään sitten workerille.
Kun worker on hakenut kuvan, se lähetetään takaisin skriptille blobina. Skripti lopulta vaihtaa vanhan kuvan URL:n web workerin luomaan blob-URL:iin.
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 }
)
})
}) Core Web Vitals -pistemäärä esiladatulla LCP-kuvalla
Your dev team is busy.
Delegate the performance architecture to a specialist. I handle the optimization track while your team ships the product.
- Parallel Workflows
- Specialized Expertise
- Faster Delivery

