Kodėl vaizdai internete vis dar yra problema
Kiekvienas iš mūsų esame patyrę tą nemalonia akimirką, kai atsidarome puslapį, o vaizdai kraunasi kaip 2005-aisiais per dial-up ryšį. Ironija ta, kad nors interneto greičiai išaugo kosmiškai, vaizdų dydžiai augo dar sparčiau. 4K ekranai, Retina displejiai, high-DPI telefonai – visa tai reikalauja vis didesnės rezoliucijos paveikslėlių. Rezultatas? Vidutinis puslapio svoris jau seniai peržengė 2MB ribą, ir didžioji dalis to – būtent vaizdai.
Bet štai kas įdomu: problema ne tik dydyje. Netgi su padoriu internetu, vartotojas mato tą nemalonų tuščią plotą arba, dar blogiau, visą puslapio layout’ą, kuris šokinėja aukštyn-žemyn, kai vaizdai pagaliau užsikrauna. Tai vadinama CLS (Cumulative Layout Shift), ir Google tai vertina kaip vieną iš pagrindinių Core Web Vitals metrikų.
Kas yra progressive image loading ir kodėl tai svarbu
Progressive image loading – tai ne viena konkreti technologija, o greičiau filosofija, kaip turėtų krautis vaizdai. Pagrindinis principas paprastas: geriau rodyti kažką iš karto, nei nieko ilgą laiką, o paskui staiga – bum – pilną vaizdą.
Tradiciškai vaizdai kraunasi nuo viršaus žemyn, eilutė po eilutės. Jei vaizdas didelis arba internetas lėtas, vartotojas ilgai žiūri į pusiau užsikrovusį paveikslėlį. Progresyvios technikos leidžia rodyti visą vaizdo plotą iš karto, tik pradžioje – labai sumažinta kokybe, kuri palaipsniui gerėja.
Praktiškai tai reiškia geresnę UX, mažesnį bounce rate ir, kas svarbu SEO kontekste, geresnius Core Web Vitals rezultatus. Google jau seniai aiškiai pasakė: puslapio greitis ir stabilumas tiesiogiai veikia reitingus. Taigi, tai ne tik gražu, bet ir naudinga verslui.
Klasikinis progressive JPEG formatas
Pradėkime nuo seniausios ir paprasčiausios technikos – progressive JPEG. Šis formatas egzistuoja jau dešimtmečius, bet daugelis developerių vis dar nežino, kad jį naudoja neteisingai arba visai nenaudoja.
Skirtumas nuo baseline JPEG paprastas: failas saugomas taip, kad pirmiausia perduodamas labai žemos kokybės visas vaizdas, paskui – vis geresnės kokybės „sluoksniai”. Vartotojas iš karto mato, kas paveiksle, nors ir neaiškiai, ir vaizdas palaipsniui „išryškėja”.
Konvertuoti į progressive JPEG galima naudojant ImageMagick:
„`bash
convert input.jpg -interlace Plane output.jpg
„`
Arba per Node.js su sharp biblioteka:
„`javascript
sharp(‘input.jpg’)
.jpeg({ progressive: true })
.toFile(‘output.jpg’);
„`
Bet čia yra niuansas: progressive JPEG kartais gali būti šiek tiek didesnis už baseline variantą, ypač jei vaizdas mažas. Todėl optimalu naudoti progressive formatą tik vaizdams, didesniems nei 10KB. Automatizuoti tai galima build proceso metu.
Blur-up technika arba LQIP metodas
LQIP (Low Quality Image Placeholder) – tai technika, kurią išpopuliarino Medium ir kitos modernios platformos. Principas: pirmiausia užkraunama mikroskopinė vaizdo versija (pvz., 20×20 pikselių), ji ištempiama per visą reikalingą plotą ir pritaikomas blur efektas. Tada fone kraunasi normalus vaizdas, o kai jis pasiruošia – smooth transition.
Efektas labai elegantiškas: vartotojas iš karto mato spalvų paletę ir bendrą kompoziciją, nors ir neaiškiai. Kai normalus vaizdas užsikrauna, jis „išryškėja” kaip fotoaparato fokusas.
Implementacija gana paprasta. Pirmiausia generuojame mini versiją:
„`javascript
sharp(‘original.jpg’)
.resize(20, 20, { fit: ‘inside’ })
.blur()
.toBuffer()
.then(data => {
const base64 = data.toString(‘base64’);
// Šitą base64 galima įterpti tiesiai į HTML
});
„`
HTML’e tai atrodo maždaug taip:
„`html

„`
CSS’e reikia pasirūpinti, kad transition būtų sklandus:
„`css
.image-wrapper {
position: relative;
overflow: hidden;
}
.placeholder {
position: absolute;
width: 100%;
height: 100%;
object-fit: cover;
transition: opacity 0.3s;
}
.actual-image {
opacity: 0;
transition: opacity 0.3s;
}
.actual-image.loaded {
opacity: 1;
}
„`
JavaScript’e sekame, kada normalus vaizdas užsikrovė:
„`javascript
const img = document.querySelector(‘.actual-image’);
img.addEventListener(‘load’, () => {
img.classList.add(‘loaded’);
setTimeout(() => {
document.querySelector(‘.placeholder’).style.opacity = ‘0’;
}, 300);
});
img.src = img.dataset.src;
„`
Modernios alternatyvos: SQIP ir primityvės
LQIP su blur efektu yra gražu, bet yra dar įdomesnių variantų. SQIP (SVG Quality Image Placeholder) naudoja SVG primitives – paprastas geometrines formas – kad atkurtų vaizdo esmę.
Technologija veikia taip: AI algoritmas analizuoja vaizdą ir bando jį atkurti naudojant 8-100 paprastų figūrų (trikampių, apskritimų, stačiakampių). Rezultatas atrodo kaip abstraktus menas, bet stebėtinai gerai perteikia originalą.
SQIP pranašumas – failas dar mažesnis nei LQIP. SVG su 10 primityvių gali sverti vos 1-2KB, o vizualiai būti įdomesnis nei sulietas blur’as.
Naudojimas:
„`bash
npm install sqip-cli -g
sqip input.jpg -n 10 -o output.svg
„`
Rezultatą galima įterpti tiesiai į HTML kaip inline SVG arba base64. Efektas primena low-poly meną ir atrodo šiuolaikiškai.
Dar vienas variantas – ThumbHash arba BlurHash. Tai algoritmai, kurie užkoduoja visą vaizdą į 20-30 simbolių string’ą. Iš šio string’o JavaScript’e galima sugeneruoti placeholder canvas elementą. Blurhash ypač populiarus – jį naudoja Mastodon, Wolt ir kitos platformos.
Native lazy loading ir Intersection Observer API
Kalbant apie progressive loading, negalima nepaminėti lazy loading – technikos, kai vaizdai kraunasi tik tada, kai vartotojas artėja prie jų. Tai ne visai tas pats kaip progressive loading, bet puikiai papildo.
Gera žinia: nuo 2020-ųjų naršyklės palaiko native lazy loading. Tai reiškia, kad užtenka pridėti vieną atributą:
„`html

„`
Naršyklė pati pasirūpins, kad vaizdas krautųsi tik prieš jam pasirodant viewport’e. Support’as puikus – virš 95% naršyklių. Bet yra niuansas: naršyklės patys sprendžia, kada pradėti krauti. Dažniausiai tai vyksta, kai vaizdas yra maždaug 1000-3000px atstumu nuo viewport’o.
Jei reikia daugiau kontrolės, galima naudoti Intersection Observer API:
„`javascript
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.add(‘loading’);
img.addEventListener(‘load’, () => {
img.classList.remove(‘loading’);
img.classList.add(‘loaded’);
});
observer.unobserve(img);
}
});
}, {
rootMargin: ’50px’ // Pradėti krauti 50px prieš pasirodant
});
document.querySelectorAll(‘img[data-src]’).forEach(img => {
imageObserver.observe(img);
});
„`
Šis metodas leidžia tiksliai kontroliuoti, kada ir kaip vaizdai kraunasi. Galima pridėti loading animacijas, error handling’ą, retry logika.
WebP, AVIF ir responsive images
Progressive loading technika veikia dar geriau, kai pats vaizdas optimizuotas. Čia į žaidimą įeina modernūs formatai ir responsive images.
WebP formatas jau seniai ne naujiena – jis suteikia 25-35% mažesnius failus nei JPEG su tokia pačia kokybe. AVIF dar naujesnis ir dar efektyvesnis – iki 50% mažesni failai. Bet support’as dar ne idealus.
Sprendimas – naudoti `
„`html
Naršyklė automatiškai pasirenka pirmą palaikomą formatą. Kombinuojant tai su progressive JPEG/WebP ir LQIP technikomis, gaunamas optimalus rezultatas.
Responsive images – dar vienas svarbus aspektas. Kam krauti 2000px pločio vaizdą į 400px telefono ekraną? `srcset` atributas leidžia naršyklei pasirinkti tinkamą dydį:
„`html

„`
Automatizuoti visų šių versijų generavimą galima build proceso metu su sharp, imagemin arba panašiomis bibliotekomis.
CDN ir image optimization services
Rankiniu būdu generuoti visas šias versijas – LQIP, progressive, WebP, AVIF, skirtingus dydžius – gali būti košmaras. Laimei, egzistuoja servisai, kurie tai daro automatiškai.
Cloudflare Images, Cloudinary, imgix, ImageKit – visi šie servisai veikia panašiai: uploadini originalą, o jie on-the-fly generuoja optimizuotas versijas. URL parametrais gali kontroliuoti formatą, dydį, kokybę, net pritaikyti efektus.
Pavyzdžiui, Cloudinary:
„`html

„`
`f_auto` automatiškai pasirenka geriausią formatą (WebP, AVIF ar JPEG), `q_auto` – optimalią kokybę, `w_800` – plotį. Servisas taip pat automatiškai generuoja progressive versijas.
Daugelis šių servisų palaiko ir LQIP generavimą. Cloudinary turi `e_blur` transformaciją, imgix – `blur` parametrą. Galima net generuoti placeholder’ius automatiškai:
„`javascript
const lqip = `https://res.cloudinary.com/demo/image/upload/
w_20,e_blur:1000,f_auto,q_auto/sample.jpg`;
„`
Kaina? Dažniausiai yra nemokamas tier’as, kurio užtenka mažesniems projektams. Didesniems – nuo $50-100 per mėnesį. Bet atsižvelgiant į sutaupytą laiką ir pagerintas metrikas, tai atsipirks.
Kai vaizdai tampa patirtimi, o ne kliūtimi
Implementavus progressive image loading technikas, skirtumas jaučiasi iš karto. Puslapiai atrodo greitesni, net jei faktinis load time nepasikeitė drastiškai. Psichologija čia vaidina didžiulį vaidmenį – kai vartotojas mato, kad kažkas vyksta (blur’as išryškėja, spalvos atsiranda), jis jaučiasi, kad puslapis responsive.
Praktiniu lygmeniu, štai ką rekomenduočiau implementuoti pirmiausia:
**Greitam startui**: Pridėk `loading=”lazy”` prie visų vaizdų, kurie ne „above the fold”. Tai 5 minučių darbas su didžiule nauda.
**Vidutiniam projektui**: Integruok LQIP techniką bent hero vaizdams ir svarbiems content vaizdams. Automatizuok generavimą build procese.
**Rimtam projektui**: Naudok image optimization CDN, generuok responsive versijas, implementuok WebP/AVIF su fallback’ais, pridėk BlurHash arba SQIP placeholder’ius.
Svarbu suprasti, kad tobulas sprendimas priklauso nuo konteksto. E-commerce svetainei su tūkstančiais produktų vaizdų reikia kitokio approach’o nei portfolio su keliais dešimtimis high-quality nuotraukų. Bet pagrindinis principas lieka tas pats: geriau rodyti kažką iš karto, nei versti vartotoją laukti.
Technologijos tobulėja, naršyklės gauna naujas galimybes, bet vartotojo kantrybė lieka ta pati – maždaug 3 sekundės. Per tą laiką reikia parodyti, kad puslapis gyvas, kad jis kraunasi, kad verta palaukti. Progressive image loading technikos – tai įrankiai, kurie padeda laimėti tas kritines sekundes.
