Bouwen aan een effectieve beeldcomponent

Een afbeeldingscomponent omvat best practices op het gebied van prestaties en biedt een kant-en-klare oplossing om afbeeldingen te optimaliseren.

Leena Sohoni
Leena Sohoni
Kara Erickson
Kara Erickson
Alex Kasteel
Alex Castle

Afbeeldingen zijn een veel voorkomende bron van prestatieknelpunten voor webapplicaties en een belangrijk aandachtsgebied voor optimalisatie. Niet-geoptimaliseerde afbeeldingen dragen bij aan het opzwellen van pagina's en zijn momenteel verantwoordelijk voor meer dan 70% van het totale paginagewicht in bytes op het 90e percentiel. Meerdere manieren om afbeeldingen te optimaliseren vereisen een intelligente "beeldcomponent" met standaard ingebouwde prestatieoplossingen.

Het Aurora- team werkte samen met Next.js om zo'n component te bouwen. Het doel was om een ​​geoptimaliseerd afbeeldingssjabloon te maken dat webontwikkelaars verder konden aanpassen. De component dient als een goed model en zet een standaard voor het bouwen van afbeeldingscomponenten in andere raamwerken, contentmanagementsystemen (CMS) en tech-stacks. We hebben samengewerkt aan een soortgelijk onderdeel voor Nuxt.js, en we werken met Angular aan beeldoptimalisatie in toekomstige versies. In dit bericht wordt besproken hoe we de Next.js Image-component hebben ontworpen en welke lessen we onderweg hebben geleerd.

Beeldcomponent als verlengstuk van afbeeldingen

Problemen en mogelijkheden voor beeldoptimalisatie

Afbeeldingen hebben niet alleen invloed op de prestaties, maar ook op de bedrijfsvoering. Het aantal afbeeldingen op een pagina was de op een na grootste voorspeller van conversies van gebruikers die websites bezochten. Sessies waarin gebruikers converteerden, hadden 38% minder afbeeldingen dan sessies waarin ze niet converteerden. Lighthouse somt meerdere mogelijkheden op om afbeeldingen te optimaliseren en webvitaliteit te verbeteren als onderdeel van de best practices-audit. Enkele van de meest voorkomende gebieden waar afbeeldingen van invloed kunnen zijn op de kernfuncties van het web en de gebruikerservaring zijn als volgt.

Afbeeldingen zonder formaat doen CLS pijn

Afbeeldingen die worden weergegeven zonder dat de grootte ervan is opgegeven, kunnen lay-outinstabiliteit veroorzaken en bijdragen aan een hoge Cumulatieve Layout Shift ( CLS ). Het instellen van de width en height op img- elementen kan lay-outverschuivingen helpen voorkomen. Bijvoorbeeld:

<img src="flower.jpg" width="360" height="240">

De breedte en hoogte moeten zo worden ingesteld dat de beeldverhouding van de weergegeven afbeelding dicht bij de natuurlijke beeldverhouding ligt. Een aanzienlijk verschil in de beeldverhouding kan ertoe leiden dat het beeld er vervormd uitziet. Een relatief nieuwe eigenschap waarmee u de beeldverhouding in CSS kunt specificeren, kan helpen om afbeeldingen responsief te vergroten, terwijl CLS wordt voorkomen.

Grote afbeeldingen kunnen LCP schaden

Hoe groter de bestandsgrootte van een afbeelding, hoe langer het downloaden duurt. Een grote afbeelding kan de 'held'-afbeelding voor de pagina zijn of het belangrijkste element in de viewport dat verantwoordelijk is voor het activeren van de Largest Contentful Paint ( LCP ). Een afbeelding die deel uitmaakt van de kritieke inhoud en het downloaden ervan lang duurt, zal de LCP vertragen.

In veel gevallen kunnen ontwikkelaars de afbeeldingsgrootte verkleinen door betere compressie en het gebruik van responsieve afbeeldingen. De attributen srcset sizes van het <img> -element helpen om afbeeldingsbestanden van verschillende formaten te voorzien. Afhankelijk van de schermgrootte en resolutie kan de browser vervolgens de juiste kiezen.

Slechte beeldcompressie kan LCP schaden

Moderne afbeeldingsformaten zoals AVIF of WebP kunnen een betere compressie bieden dan de veelgebruikte JPEG- en PNG-formaten. Betere compressie verkleint de bestandsgrootte in sommige gevallen met 25% tot 50% voor dezelfde kwaliteit van de afbeelding. Deze reductie leidt tot snellere downloads met minder dataverbruik. De app moet moderne beeldformaten aanbieden aan browsers die deze formaten ondersteunen.

Het laden van onnodige afbeeldingen doet LCP pijn

Afbeeldingen onder de vouw of niet in de viewport worden niet aan de gebruiker weergegeven wanneer de pagina wordt geladen. Ze kunnen worden uitgesteld, zodat ze niet bijdragen aan het LCP en dit vertragen. Lazy-loading kan worden gebruikt om dergelijke afbeeldingen later te laden terwijl de gebruiker ernaartoe scrollt.

Optimalisatie-uitdagingen

Teams kunnen de prestatiekosten als gevolg van de eerder genoemde problemen evalueren en best practice-oplossingen implementeren om deze te overwinnen. In de praktijk gebeurt dit echter vaak niet en inefficiënte afbeeldingen blijven het internet vertragen. Mogelijke redenen hiervoor zijn onder meer:

  • Prioriteiten : Webontwikkelaars hebben meestal de neiging zich te concentreren op code, JavaScript en gegevensoptimalisatie. Als zodanig zijn ze zich mogelijk niet bewust van problemen met afbeeldingen of hoe ze deze kunnen optimaliseren. Afbeeldingen gemaakt door ontwerpers of geüpload door gebruikers staan ​​mogelijk niet hoog op de prioriteitenlijst.
  • Out-of-the-box oplossing : Zelfs als ontwikkelaars zich bewust zijn van de nuances van beeldoptimalisatie, kan het ontbreken van een alles-in-één out-of-the-box oplossing voor hun framework of tech-stack een afschrikmiddel zijn.
  • Dynamische afbeeldingen : Naast statische afbeeldingen die deel uitmaken van de applicatie, worden dynamische afbeeldingen geüpload door gebruikers of afkomstig uit externe databases of CMS's. Het kan een uitdaging zijn om de grootte van dergelijke afbeeldingen te definiëren als de bron van de afbeelding dynamisch is.
  • Overbelasting van markeringen : Oplossingen voor het opnemen van de afbeeldingsgrootte of srcset voor verschillende formaten vereisen extra markeringen voor elke afbeelding, wat vervelend kan zijn. Het srcset attribuut werd in 2014 geïntroduceerd, maar wordt tegenwoordig door slechts 26,5% van de websites gebruikt . Bij gebruik van srcset moeten ontwikkelaars afbeeldingen in verschillende formaten maken. Tools zoals just-gimme-an-img kunnen helpen, maar moeten voor elke afbeelding handmatig worden gebruikt.
  • Browserondersteuning : Moderne afbeeldingsformaten zoals AVIF en WebP creëren kleinere afbeeldingsbestanden, maar hebben speciale behandeling nodig in browsers die deze niet ondersteunen. Ontwikkelaars moeten strategieën gebruiken zoals contentonderhandeling of het <picture > -element, zodat afbeeldingen aan alle browsers worden aangeboden.
  • Complicaties voor Lazy Loading : Er zijn meerdere technieken en bibliotheken beschikbaar om lazyloading voor afbeeldingen onder de vouw te implementeren. Het kiezen van de beste kan een uitdaging zijn. Ontwikkelaars weten mogelijk ook niet wat de beste afstand tot de "vouw" is om uitgestelde afbeeldingen te laden. Verschillende viewport-groottes op apparaten kunnen dit verder compliceren.
  • Veranderend landschap : Nu browsers nieuwe HTML- of CSS-functies beginnen te ondersteunen om de prestaties te verbeteren, kan het voor ontwikkelaars moeilijk zijn om ze allemaal te evalueren. Chrome introduceert bijvoorbeeld de functie Fetch Priority als een Origin-proefversie . Het kan worden gebruikt om de prioriteit van specifieke afbeeldingen op de pagina te verhogen. Over het algemeen zouden ontwikkelaars het gemakkelijker vinden als dergelijke verbeteringen op componentniveau zouden worden geëvalueerd en geïmplementeerd.

Beeldcomponent als oplossing

De beschikbare mogelijkheden om afbeeldingen te optimaliseren en de uitdagingen bij het individueel implementeren ervan voor elke toepassing brachten ons op het idee van een beeldcomponent. Een afbeeldingscomponent kan best practices inkapselen en afdwingen. Door het <img> -element te vervangen door een afbeeldingscomponent kunnen ontwikkelaars hun problemen met de beeldprestaties beter aanpakken.

Het afgelopen jaar hebben we met het Next.js- framework gewerkt om hun Image-component te ontwerpen en te implementeren . Het kan als volgt worden gebruikt als drop-in vervanging voor de bestaande <img> -elementen in Next.js-apps.

// Before with <img> element:
function Logo() {
  return <img src="/logo.jpg" alt="logo" height="200" width="100" />
}

// After with image component:
import Image from 'next/image'

function Logo() {
  return <Image src="/logo.jpg" alt="logo" height="200" width="100" />
}

De component probeert beeldgerelateerde problemen generiek aan te pakken via een rijke reeks functies en principes. Het bevat ook opties waarmee ontwikkelaars het kunnen aanpassen aan verschillende afbeeldingsvereisten.

Bescherming tegen lay-outverschuivingen

Zoals eerder besproken veroorzaken afbeeldingen zonder formaat lay-outverschuivingen en dragen ze bij aan CLS. Bij het gebruik van de Next.js Image-component moeten ontwikkelaars een afbeeldingsgrootte opgeven met behulp van de kenmerken width en height om eventuele lay-outverschuivingen te voorkomen. Als de grootte onbekend is, moeten ontwikkelaars layout=fill opgeven om een ​​afbeelding zonder grootte weer te geven die zich in een container met grootte bevindt. Als alternatief kunt u statische afbeeldingen importeren om de grootte van de daadwerkelijke afbeelding op de harde schijf tijdens het bouwen op te halen en deze in de afbeelding op te nemen.

// Image component with width and height specified
<Image src="/logo.jpg" alt="logo" height="200" width="100" />

// Image component with layout specified
<Image src="/hero.jpg" layout="fill" objectFit="cover" alt="hero" />

// Image component with image import
import Image from 'next/image'
import logo from './logo.png'

function Logo() {
  return <Image src={logo} alt="logo" />
}

Omdat ontwikkelaars de afbeeldingscomponent niet zonder grootte kunnen gebruiken, zorgt het ontwerp ervoor dat ze de tijd nemen om de afbeeldingsgrootte te overwegen en lay-outverschuivingen te voorkomen.

Faciliteer het reactievermogen

Om afbeeldingen op verschillende apparaten responsief te maken, moeten ontwikkelaars de kenmerken srcset sizes instellen in het <img> -element. We wilden deze inspanning verminderen met de component Afbeelding. We hebben de component Next.js Image ontworpen om de attribuutwaarden slechts één keer per toepassing in te stellen. We passen ze toe op alle exemplaren van de component Afbeelding op basis van de lay-outmodus. We hebben een driedelige oplossing bedacht:

  1. deviceSizes eigenschap: deze eigenschap kan worden gebruikt om breekpunten eenmalig te configureren op basis van de apparaten die gemeenschappelijk zijn voor het gebruikersbestand van de toepassing. De standaardwaarden voor breekpunten zijn opgenomen in het configuratiebestand.
  2. imageSizes eigenschap: Dit is ook een configureerbare eigenschap die wordt gebruikt om de afbeeldingsgrootten op te halen die overeenkomen met de breekpunten van de apparaatgrootte.
  3. layout kenmerk in elke afbeelding: dit wordt gebruikt om aan te geven hoe de eigenschappen deviceSizes en imageSizes voor elke afbeelding moeten worden gebruikt. De ondersteunde waarden voor de lay-outmodus zijn fixed , fill , intrinsic en responsive

Wanneer een afbeelding wordt opgevraagd met de lay-outmodi responsive of fill , identificeert Next.js de afbeelding die moet worden weergegeven op basis van de grootte van het apparaat dat de pagina opvraagt ​​en worden de srcset en sizes in de afbeelding op de juiste manier ingesteld.

De volgende vergelijking laat zien hoe de lay-outmodus kan worden gebruikt om de grootte van de afbeelding op verschillende schermen te regelen. We hebben een demo-afbeelding gebruikt die is gedeeld in de Next.js-documenten, bekeken op een telefoon en een standaardlaptop.

Laptopscherm Telefoon scherm
Lay-out = Intrinsiek: schaalt omlaag om te passen in de breedte van de container op kleinere viewports. Schaalt niet verder dan de intrinsieke grootte van de afbeelding op een grotere viewport. Containerbreedte staat op 100%
Afbeelding van bergen weergegeven zoals deze isAfbeelding van bergen verkleind
Layout = Opgelost: afbeelding reageert niet. Breedte en hoogte worden op dezelfde manier vastgesteld als ` ` element, ongeacht het apparaat waarop het wordt weergegeven.
Afbeelding van bergen weergegeven zoals deze isHet weergegeven bergenbeeld past niet op het scherm
Lay-out = Responsief: Schaal omlaag of omhoog, afhankelijk van de breedte van de container op verschillende viewports, waarbij de beeldverhouding behouden blijft.
Afbeelding van bergen vergroot zodat deze op het scherm pastAfbeelding van bergen verkleind zodat deze op het scherm past
Indeling = Opvulling: breedte en hoogte uitgerekt om de bovenliggende container te vullen. (Ouder `
` breedte is in dit voorbeeld ingesteld op 300*500)
Afbeelding van bergen weergegeven in het formaat 300*500Afbeelding van bergen weergegeven in het formaat 300*500
Afbeeldingen weergegeven voor verschillende lay-outs

Zorg voor ingebouwde lazyloading

De component Image biedt standaard een ingebouwde, performante lazyload- oplossing. Als je het <img> -element gebruikt, zijn er een paar native opties voor lazyloading, maar deze hebben allemaal nadelen waardoor ze lastig te gebruiken zijn. Een ontwikkelaar kan een van de volgende lazyload-benaderingen gebruiken:

  • Geef het loading op: dit is eenvoudig te implementeren, maar wordt momenteel in sommige browsers niet ondersteund .
  • Gebruik de Intersection Observer API : het bouwen van een aangepaste lazy-loading-oplossing vereist inspanning en een doordacht ontwerp en implementatie. Ontwikkelaars hebben hier misschien niet altijd de tijd voor.
  • Importeer een bibliotheek van derden om afbeeldingen lui te laden: Er kan extra inspanning nodig zijn om een ​​geschikte bibliotheek van derden voor lui laden te evalueren en te integreren.

In de component Next.js Image is het laden standaard ingesteld op "lazy" . Lazyloading wordt geïmplementeerd met behulp van Intersection Observer, dat beschikbaar is in de meeste moderne browsers . Ontwikkelaars hoeven niets extra's te doen om dit in te schakelen, maar kunnen het indien nodig uitschakelen.

Belangrijke afbeeldingen vooraf laden

Heel vaak zijn LCP-elementen afbeeldingen, en grote afbeeldingen kunnen LCP vertragen. Het is een goed idee om kritieke afbeeldingen vooraf te laden , zodat de browser die afbeelding eerder kan ontdekken. Wanneer u een <img> -element gebruikt, kan er als volgt een preload-hint in de HTML-header worden opgenomen.

<link rel="preload" as="image" href="important.png">

Een goed ontworpen afbeeldingscomponent zou een manier moeten bieden om de laadvolgorde van afbeeldingen aan te passen, ongeacht het gebruikte raamwerk. In het geval van de Next.js Image-component kunnen ontwikkelaars een afbeelding aangeven die een goede kandidaat is voor vooraf laden met behulp van het priority van de images-component.

<Image src="/hero.jpg" alt="hero" height="400" width="200" priority />

Het toevoegen van een priority vereenvoudigt de opmaak en is handiger in gebruik. Ontwikkelaars van afbeeldingscomponenten kunnen ook opties verkennen om heuristieken toe te passen om het vooraf laden van afbeeldingen boven de vouw op de pagina te automatiseren die aan specifieke criteria voldoen.

Stimuleer hoogwaardige beeldhosting

Image CDN's worden aanbevolen voor het automatiseren van beeldoptimalisatie, en ze ondersteunen ook moderne beeldformaten zoals WebP en AVIF. De Next.js Image-component gebruikt standaard een image-CDN met behulp van een loader-architectuur . In het volgende voorbeeld ziet u dat de lader configuratie van het CDN in het configuratiebestand Next.js mogelijk maakt.

module.exports = {
  images: {
    loader: 'imgix',
    path: 'https://ImgApp/imgix.net',
  },
}

Met deze configuratie kunnen ontwikkelaars relatieve URL's in de afbeeldingsbron gebruiken, en het raamwerk zal de relatieve URL samenvoegen met het CDN-pad om de absolute URL te genereren. Populaire afbeeldings-CDN's zoals Imgix , Cloudinary en Akamai worden ondersteund. De architectuur ondersteunt het gebruik van een aangepaste cloudprovider door een aangepaste loader voor de app te implementeren.

Ondersteuning van zelfgehoste afbeeldingen

Er kunnen situaties zijn waarin websites geen afbeeldings-CDN's kunnen gebruiken. In dergelijke gevallen moet een afbeeldingscomponent zelf-gehoste afbeeldingen ondersteunen. De Next.js Image-component gebruikt een afbeeldingsoptimalisatie als ingebouwde afbeeldingsserver die een CDN-achtige API biedt. De optimalisatie gebruikt Sharp voor transformaties van productieafbeeldingen als deze op de server is geïnstalleerd. Deze bibliotheek is een goede keuze voor iedereen die zijn eigen pijplijn voor beeldoptimalisatie wil opbouwen.

Ondersteuning van progressieve belasting

Progressief laden is een techniek die wordt gebruikt om de interesse van gebruikers vast te houden door een tijdelijke afbeelding weer te geven, die doorgaans van aanzienlijk lagere kwaliteit is, terwijl de daadwerkelijke afbeelding wordt geladen. Het verbetert de waargenomen prestaties en verbetert de gebruikerservaring. Het kan worden gebruikt in combinatie met lazyloading voor afbeeldingen onder de vouw of voor afbeeldingen boven de vouw.

De component Next.js Image ondersteunt progressief laden van de afbeelding via de placeholder- eigenschap. Dit kan worden gebruikt als LQIP (plaatsaanduiding voor afbeeldingen van lage kwaliteit) voor het weergeven van een afbeelding van lage kwaliteit of wazig terwijl de daadwerkelijke afbeelding wordt geladen.

Invloed

Met alle bovenstaande optimalisaties erin verwerkt, hebben we succes gezien met de Next.js Image-component in productie en werken we ook samen met andere tech-stacks aan vergelijkbare afbeeldingscomponenten.

Toen Leboncoin hun oude JavaScript-frontend naar Next.js migreerde , hebben ze ook hun afbeeldingspijplijn geüpgraded om de Next.js Image-component te gebruiken. Op een pagina die van <img> naar next/image migreerde, daalde de LCP van 2,4 s naar 1,7 s. Het totaal aantal gedownloade afbeeldingsbytes voor de pagina ging van 663 kB naar 326 kB (met ~100 kB aan lui geladen afbeeldingsbytes).

Les geleerd

Iedereen die een Next.js-app maakt, kan profiteren van het gebruik van de Next.js Image-component voor optimalisatie. Als u echter soortgelijke prestatieabstracties voor een ander raamwerk of CMS wilt bouwen, volgen hier enkele lessen die we onderweg hebben geleerd en die nuttig kunnen zijn.

Veiligheidskleppen kunnen meer kwaad dan goed doen

In een vroege release van de component Next.js Image hebben we een attribuut unsized geleverd waarmee ontwikkelaars de vereiste grootte konden omzeilen en afbeeldingen met niet-gespecificeerde afmetingen konden gebruiken. We dachten dat dit nodig zou zijn in gevallen waarin het onmogelijk was om vooraf de hoogte of breedte van de afbeelding te kennen. We merkten echter dat gebruikers het unsized attribuut in GitHub-problemen aanbeveelden als een allesomvattende oplossing voor problemen met de vereiste grootte, zelfs in gevallen waarin ze het probleem konden oplossen op een manier die de CLS niet verergerde. Vervolgens hebben we het kenmerk unsized afgeschaft en verwijderd.

Scheid nuttige wrijving van zinloze ergernis

De vereiste voor het formaat van een afbeelding is een voorbeeld van 'nuttige wrijving'. Het beperkt het gebruik van de component, maar biedt in ruil daarvoor enorme prestatievoordelen. Gebruikers zullen de beperking gemakkelijk accepteren als ze een duidelijk beeld hebben van de potentiële prestatievoordelen. Daarom is het de moeite waard om deze afweging uit te leggen in de documentatie en ander gepubliceerd materiaal over de component.

U kunt echter oplossingen voor dergelijke wrijving vinden zonder dat dit ten koste gaat van de prestaties. Tijdens de ontwikkeling van de component Next.js Image ontvingen we bijvoorbeeld klachten dat het vervelend was om formaten op te zoeken voor lokaal opgeslagen afbeeldingen. We hebben statische afbeeldingsimporten toegevoegd, die dit proces stroomlijnen door tijdens het bouwen automatisch afmetingen voor lokale afbeeldingen op te halen met behulp van een Babel-plug-in.

Zorg voor een evenwicht tussen gemaksfuncties en prestatie-optimalisaties

Als uw afbeeldingscomponent niets anders doet dan "nuttige wrijving" opleggen aan zijn gebruikers, zullen ontwikkelaars de neiging hebben om deze niet te willen gebruiken. We ontdekten dat prestatiekenmerken zoals beeldgrootte en het automatisch genereren van srcset waarden het belangrijkst waren. Op ontwikkelaars gerichte gemaksfuncties zoals automatisch lui laden en ingebouwde wazige tijdelijke aanduidingen wekten ook interesse in de Next.js Image-component.

Stel een routekaart op voor functies om de acceptatie te stimuleren

Het bouwen van een oplossing die perfect werkt voor alle situaties is erg moeilijk. Het kan verleidelijk zijn om iets te ontwerpen dat goed werkt voor 75% van de mensen en vervolgens tegen de andere 25% te zeggen dat "in deze gevallen dit onderdeel niets voor jou is."

In de praktijk blijkt deze strategie haaks te staan ​​op jouw doelstellingen als componentontwerper. U wilt dat ontwikkelaars uw component adopteren om te profiteren van de prestatievoordelen ervan. Dit is moeilijk te doen als er een contingent gebruikers is die niet kunnen migreren en zich buiten het gesprek buitengesloten voelen. Ze zullen waarschijnlijk hun teleurstelling uiten, wat leidt tot negatieve percepties die de adoptie beïnvloeden.

Het is raadzaam om voor uw component een routekaart te hebben die alle redelijke gebruiksscenario's op de lange termijn dekt. Het helpt ook om in de documentatie expliciet te zijn over wat niet wordt ondersteund en waarom, om verwachtingen te scheppen over de problemen die de component moet oplossen.

Conclusie

Het gebruik en de optimalisatie van afbeeldingen zijn ingewikkeld. Ontwikkelaars moeten de balans vinden tussen prestaties en kwaliteit van afbeeldingen en tegelijkertijd een geweldige gebruikerservaring garanderen. Dit maakt beeldoptimalisatie een dure onderneming met grote impact.

In plaats van elke app elke keer het wiel opnieuw te laten uitvinden, hebben we een best practices-sjabloon bedacht die ontwikkelaars, frameworks en andere tech-stacks konden gebruiken als referentie voor hun eigen implementaties. Deze ervaring zal inderdaad waardevol blijken als we andere raamwerken ondersteunen op het gebied van hun imagocomponenten.

De Next.js Image-component heeft met succes de prestatieresultaten in Next.js-applicaties verbeterd, waardoor de gebruikerservaring is verbeterd. Wij zijn van mening dat het een geweldig model is dat goed zou werken in het bredere ecosysteem, en we horen graag van ontwikkelaars die dit model in hun projecten willen overnemen.