FrontEnd.ro logo

Imagini

Doodle cu rama unei imagini

Cred că putem cu toții admite că un Internet fără imagini ar fi destul de plictisitor. Deci hai să încheiem așteptarea și să vedem cum adăugăm imagini în site-urile noastre și care sunt cele mai bune practici legate de acest subiect.

#Elementul <img>

Primul și cel mai comun mod de a adăuga o imagine este folosind elementul img alături de 2 atribute:

  • src: pentru a specifica URL-ul imaginii
  • alt: pentru a descrie conținutul imaginii - în caz că aceasta nu poate fi încărcată

<img 
  src="golden-retrieve-and-ball.jpg" 
  alt="Golden retriever biting blue ball"
/>

Dacă imaginea se află la acel URL și avem conexiune la internet vom obține o pagină ca în imaginea din stânga. Însă, dacă browserul nu a putut încărca imaginea, vom vedea descrierea text ca în exemplul din dreapta.

Imagine încărcată cu succes într-o pagină Web
Imagine încărcată cu succes într-o pagină Web
Demo complet
Descrierea text a imaginii, dacă aceasta nu a putut fi încărcată
Descrierea text a imaginii, dacă aceasta nu a putut fi încărcată
Demo complet

Este foarte important să nu uităm de atributul alt. Pe lângă cazul menționat mai sus, acesta ajută și persoanele cu dizabilități ce consumă conținut Web via screen readere.

De aceea, o pagină ce conține imagini fără atributul alt nu este considerată validă.

#Width & Height

De multe ori imaginile de pe site-uri își vor adapta dimensiunea în funcție de ecranul dispozitivului folosit: mai mici pe telefoane și tablete, mai mari pe laptop-uri și desktop-uri.

În galeria de mai jos avem 2 imagini pe fiecare rând, fiecare ocupând exact 45% din lățimea ecranului - indiferent care e aceasta.

Galerie cu 2 imagini pe fiecare rând
Galerie cu 2 imagini pe fiecare rând
Demo complet

Însă există situații în care o imagine va avea aceleași dimensiuni fixe indiferent de dispozitiv. Un exemplu ar putea fi logo-ul unei companii aflat de obicei în <header>-ul paginii.

Imagine cu dimensiunile fixe
Imagine cu dimensiunile fixe
Demo complet

În astfel de cazuri, în care știm dinainte dimensiunea, este recomandat să adăugam și atributele width și height.

<img 
  src="logo.png" 
  width="212" 
  height="70"
  alt="FrontEnd.ro logo" 
/>

Astfel, browser-ul va ști dimensiunile imaginii înainte de a o descărca iar experiența utilizatorilor va fi extrem de fluidă. Astfel evităm situația de mai jos unde textul se re-aranjează după încărcarea imaginii - problemă cunoscută sub numele de content/layout shifting.

Conținutul text se re-aranjează după încărcarea imaginii
Demo complet

Ce am arătat până acum reprezintă fundamentele imaginilor în HTML. Sunt lucrurile pe care le vei folosi în marea majoritate a cazurilor...

Mai jos continuăm să discutăm despre diverse tehnici pentru a optimiza servirea imaginilor și a oferi cea mai bună experiență posibilă, care la rândul ei va mări șansele ca proiectul/business-ul nostru să aibă succes.

#Lazy loading

Most of the images on the web are downloaded, decoded and rendered only never to be seen, as [...] the user never scrolled that far. - Yoav Weiss

Citatul de mai sus a rămas - din păcate - la fel de adevărat... De câte ori nu ai deschis o pagină Web și apoi ai ieșit de acolo fără a citi mai mult de primul paragraf?

În background însă, browser-ul a încărcat toate imaginile, ceea ce e o risipă pentru că noi nu le-am văzut pe toate. Ideal ar fi să încărcăm imaginile doar atunci când avem nevoie de ele.

Pentru a rezolva această problemă vom folosi atributul loading și valoarea lazy.

<img 
  alt="Eiffel tower during night"
  src="eiffel-tower-during-night.jpg"
  loading="lazy"
/>

Acum browserul va descărca imaginea doar când ne "apropiem" de ea. Fiecare browser are propriile metrici legate de ce înseamnă această apropiere, însă nu trebuie să ne batem capul cu asta. Regula generală e să adăugăm atributul loading="lazy" dacă avem multe imagini în pagină.

#Imagini responsive

Am pornit de la imagini simple, am optimizat experiența folosind atributele width/height iar apoi am reușit să incărcăm doar imaginile de care avem nevoie folosind atributul loading . Acum e momentul să mergem un pas mai departe în călătoria spre performanță și să încărcăm imaginea cea mai potrivită din punct de vedere al rezoluției.

Imagine full-screen intr-o pagină Web
Imagine full-screen intr-o pagină Web

La ce rezoluție salvăm imaginea pentru a fi 100% clară pe toate dispozitivele?

Știind că site-ul poate fi văzut atât de pe dispozitive mobile, cu ecrane mai mici, cât și de pe desktop-uri sau chiar televizoare, o primă soluție ar fi să salvăm imaginea la o rezoluție cât mai înaltă - să zicem 4K- pentru a ne asigura că totul e ok.

Deși soluția ar funcționa, ea nu e deloc eficientă, căci vom încărca mereu aceeași imagine de rezoluție înaltă și mulți MBs. Pe telefon de exemplu, unde ecranul e mai mic, nu avem nevoie de toți cei 8+ milioane de pixeli (3840 x 2160). Dacă luăm în calcul și conexiunea la internet, experiența poate arăta așa:

Încărcarea unei imagini mari pe o conexiune înceată
Demo complet

Ideal ar fi ca pe telefon să încărcăm exact aceeași imagine dar la o rezoluție mai mică, pe tabletă la o rezoluție mijlocie și la o rezoluție cât mai inaltă pe ecrane mari: desktop and beyond.

#Atributul srcset

Din fericire putem rezolva această problemă folosindu-ne de atributele srcset și sizes. Hai să luăm imaginea noastră și să facem resize la 3 rezoluții diferite:

Aceeași imagine în 3 dimensiuni diferite
Aceeași imagine în 3 dimensiuni diferite

Pe Windows putem face resize cu Paint iar pe MacOS folosind Preview App.

Apoi vom adăuga atributul srcset unde definim diferitele surse ale imaginii împreună cu dimensiunea (în lățime) a fiecăreia. Iar cu atributul sizes vom specifica ce dimensiuni va avea imaginea în funcție de dimensiunea ecranului:

<img srcset="
    /red_bycicle__high.jpg 4000w,
    /red_bycicle__med.jpg 2000w,
    /red_bycicle__low.jpg 800w,
  " 
  sizes="(max-width: 800px) 700px, 1000px"
  alt="Red bycicle wheel"
  src="/red_bycicle__med.jpg"
/>

Șiiii voilà. Dacă mergem în modul responsive - și ținem tabul network deschis vom observa cum diferite surse ale imaginii se încarcă la rezoluții diferite.

Diferite surse ale imaginii se încarcă la rezoluții diferite
Demo complet

O imagine cu srcset="" trebuie să conțină și atributul src="" pentru a fi validă. Motivul îl reprezintă browserele mai vechi ce nu înțeleg atributul srcset, așadar îl vor ignora și vor arăta imaginea de la src.

Te încurajăm să experimentezi și cu opțiunea DRP (Device Pixel Ratio) De exemplu, cu o valoare de 2 și o lățime de 650px se va incărca imaginea red_bycicle__med.jpg.

#Proprietatea image-set

Până de curând singurul mod de a face imaginile din CSS (cele puse prin background-image:) responsive a fost să punem mai multe reguli @media-query și să specificăm pentru fiecare o altă imagine.

Dar odată cu introducerea proprietății image-set() putem lăsa browserul să aleagă cea mai bună imagine în funcție de rezoluția ecranului:

.cover {
  height: 50vh;

  background-image: -webkit-image-set(
    url("red_bycicle__low.jpg") 1x,
    url("red_bycicle__med.jpg") 2x, 
    url("red_bycicle__high.jpg") 4x
  );

  background-image: image-set(
    url("red_bycicle__low.jpg") 1x,
    url("red_bycicle__med.jpg") 2x, 
    url("red_bycicle__high.jpg") 4x
  );
}
              

Această proprietate nu are încă suport nativ în toate Browserele, așa că în exemplul de mai sus am prefixat regulat cu -webkit pentru a funcționa și în Chrome sau Safari.

#Elementul <picture>

După cum ai văzut până acum, elementul <img> - deși destul de simplu în utilizare - ne oferă mai multe funcționalități care ne permit să optimizăm imaginile și experiența utilizatorilor. Cu toate acestea, a mai rămas totuși o ultimă optimizare, și anume folosirea unor formate moderne pentru imagini.

De ce am vrea alte formate? Nu sunt JPG sau PNG de ajuns?

Hmmm.... nu chiar. Există formate mai moderne precum WebP sau AVIF care oferă aceeași calitate a imaginii la o dimensiune mai mică. Uite diferențele de dimensiune ale acestei imagini în funcție de format:

JPGWebPAVIF
4000 x 26662.7MB2MB376kb
2000 x 1333947kb838kb181kb
800 x 533169kb150kb35kb

După cum vezi formatele WebP și AVIF sunt mai mici decât JPG sau PNG, deci imaginile în acest format se vor incărca mai repede decât celelalte. Problema este că nu toate browserele înțeleg aceste noi formate.

După cum vedem pe Can I use... - AVIF are suport doar în ultimele versiuni de Google Chrome în timp ce WebP este mai comun însă tot lipsește din IOS 13 sau Internet Explorer 11.

Suportul browserelor pentru formatul WebP
Suportul browserelor pentru formatul WebP
Demo complet
Suportul browserelor pentru formatul Avif
Suportul browserelor pentru formatul Avif
Demo complet

Deci avem nevoie de o modalitate prin care browsere care înțeleg WebP sau Avif să descarce aceste formate, în timp ce celelalte să rămână la JPG. Această tehnică se numește general graceful degradation.

Thankfully, această soluție ne este permisă de tag-ul <picture>, unde putem specifica mai multe surse pentru o imagine și să lăsăm browserul să o aleagă pe cea pe care o înțelege.

<picture>
  <source 
    srcset="
      red_bycicle__high.avif 4000w, 
      red_bycicle__med.avif 2000w, 
      red_bycicle__low.avif 800w" 
    type="image/avif">

  <source 
    srcset="
      red_bycicle__high.webp 4000w, 
      red_bycicle__med.webp 2000w, 
      red_bycicle__low.webp 800w"
    type="image/webp">

  <source 
    srcset="
      red_bycicle__high.jpg 4000w, 
      red_bycicle__med.jpg 2000w, 
      red_bycicle__low.jpg 800w" 
    type="image/jpeg">

  <img 
    alt="Red bycicle wheel"
    loading="lazy"
    srcset="
      red_bycicle__high.jpg 4000w, 
      red_bycicle__med.jpg 2000w, 
      red_bycicle__low.jpg 800w
    "
    src="red_bycicle__med.jpg"
  >
</picture>

Ordinea elementelor <source> este extrem de importantă căci browserul le va parcurge de sus-in-jos și o va alege pe prima compatibilă. De asemenea, în fiecare dintre ele trebuie adăugat - via atributul srcset - mai multe surse de dimensiuni diferite. Astfel browserul nu alege numai formatul cel mai bun, cât și dimensiunea optimă a imaginii. Best of both worlds! 💪

PS: poate ai observat acel ultim <img> tag. Ei bine, avem nevoie de el pentru a specifica descrierea imaginii - în caz că aceasta nu poate fi încărcată, cât și pentru eventuala adaugare a unor atribute extra - cum ar fi loading. Iar în cazurile mai rare în care utilizatorii folosesc browsere destul de vechi, ce nu înțeleg elementul <picture>, acestea vor înțelege totuși tag-ul <img> și îl vor arăta pe acesta.

Resurse suplimentare

  1. Documentația completă a elementului <img> pe MDN
  2. Documentația completă a elementului <picture> pe MDN
  3. Mai multe detalii despre imagini Responsive

#Exerciții