Angi bildestørrelse for å unngå layout shift
Gi info til nettleseren om hvor stor plass bildet kommer til å ta
Photo by Designecologist from Pexels
Ved å angi bildestørrelse på IMG-tagen så kan nettleseren kalkulere plassen som bildet kommer til å bruke før det vises. På den måten kan man forhindre layout shift mens innholdet på en nettside lastes.
Jeg vet ikke om et godt norsk uttrykk som kan erstatte layout shift. Så dette innlegget blir en grei kombinasjon av norsk og engelsk. Har du et godt uttrykk jeg kan bruke så finner du meg på Twitter.
Hva er layout shift?
Layout shift er når besøkende opplever at nettsiden "hopper" etter hvert som innholdet lastes inn. Spesielt når bilder lastes inn tregt og vises etter hvert som de er lastet inn. Det er ikke nødvendigvis så heldig. Det oppleves irriterende for brukeren og det kan føre til uheldig bruk av nettsiden.
Hvorfor ønsker man å forhindre layout shift?
Det eksempelet som mange kjenner seg igjen i er at man skal trykke på en lenke eller en knapp og i det samme øyeblikket blir et bilde lastet inn slik at lenken eller knappen flytter på seg. I værste tilfelle fører det til at brukeren trykker på feil lenke eller knapp fordi et annet element på nettsiden havner på det stedet musepeker eller pekefinger befinner seg.
Tilbakeblikk
Jeg har jobbet med frontend såpass mange år at jeg enkelte ganger bare innfinner meg i at enkelte ting bare er slik de er. Spoler vi noen år tilbake så kunne man ikke bruke attributter på IMG-tagen for høyde og bredde kombinert med at man skalerte bildet via CSS. Det var heller ikke utbredt støtte for å laste inn ulike bildestørrelser.
Så man endte med å laste inn største variant og skalerte det med CSS for å få det til å passe inn på ulike skjermstørrelser. Det var datidens løsning på responsive bilder.
Nåtiden
Verden går fremover og nettleserne endrer seg. Kravene endrer seg og det fokuseres mer og mer på brukeren og det tekniske for å presentere innhold til brukeren.
Ved en tilfeldighet så fant jeg ut noe som er relativt gammelt nytt. Dagens nettlesere kan kombinere attributter på IMG-tagen og skalering via CSS.
Hvordan kom jeg over det? Vel, du vet den lille kløen som er såpass irriterende at det bare må gjøres noe? Ved en tilfeldighet så jeg at jeg hadde igjen et lite punkt for at bloggen min skulle kunne oppnå 100% score i Lighthouse. Det punktet innebar å sette på attributter for høyde og bredde på IMG-tagen.
Løsningen
<img src="bilde.jpg" width="800" height="600" />
Fallgruve
Det er fristende å sette bredde til 100% og høyde til auto. Det vil virke, for innen nettleseren faktisk har lastet bildet så vet ikke nettleseren bildet størrelse og kan dermed ikke sette av plassen når siden tegnes opp.
Anbefalt lesning
https://stackoverflow.com/a/65077326
Sanity.io
For min del som bruker Sanity.io så kan jeg hente ut bildets størrelse (originalt eller skalert) og forholdstallet mellom høyde og bredde. Det kan jeg bruke til å sette på attributtene på IMG-tagen kombinert med lazy load av bildet.
<img
:alt="image.alt"
:width="placeholder.width"
:height="placeholder.height"
v-lazy="$urlForImage(image, $static.metadata.sanityOptions).width(this.width).quality(this.imageQuality).auto('format').url()"
/>
En liten detalj
Du la kanskje ikke merke til det. Men mens nettleseren din lastet inn bildet i toppen av dette innlegget så var plassen til bildet allerede satt av og med hovedfargene til bildet?
Via Sanity.io så kan man hente ut en rekke metadata om bildet. Så jeg henter ut på forhånd to hovedfarger og størrelser. Det brukes til å sette av plass til bilde med en tekst som sier at bildet lastes inn.
Den hele og fulle malen i skrivende stund (inkl. lazy load av bilde, Tailwind CSS klasser). Kort forklart: en DIV som stilsettes basert på bildets størrelse (høyde- og breddeforhold) og farger som er hentet fra bildet. Ikke den peneste koden, men den funker da.
<template>
<div
class="w-full"
ref="figure"
>
<div
v-if="showPlaceholder"
:style="`width: ${placeholder.width}px; height: ${placeholder.height}px; background-color: ${colors.dominant.background}; color: ${colors.dominant.foreground}`"
class="mx-auto flex justify-center items-center"
>
<div v-if="showError" class="text-center p-4 text-xl lg:text-5xl">
Kan ikke vise bilde
</div>
<div v-else class="text-center p-4 text-xl lg:text-5xl">
Laster inn bilde
</div>
</div>
<img
:alt="image.alt"
:width="placeholder.width"
:height="placeholder.height"
v-lazy="$urlForImage(image, $static.metadata.sanityOptions).width(this.width).quality(this.imageQuality).auto('format').url()"
/>
<p
v-if="showCaptionText"
v-text="image.caption"
class="text-xs mx-1 mt-1 text-right text-gray-500"
>
</p>
</div>
</template>
<script>
export default {
props: ['image','width','quality','showCaptionText'],
data() {
return {
aspectRatio: this.image.asset.metadata ? this.image.asset.metadata.dimensions.aspectRatio : 1.5,
placeholder: {
width: 0,
height: 0,
},
showPlaceholder: true,
showError: false,
colors: {
dominant: {
background: this.image.asset.metadata ? this.image.asset.metadata.palette.dominant.background : '#bbb',
foreground: this.image.asset.metadata ? this.image.asset.metadata.palette.dominant.foreground : '#111'
}
},
imageQuality: this.quality ? this.quality : 75
}
},
methods: {
calculateHeight() {
if (this.aspectRatio) {
const that = this
that.showDiv = true
that.$nextTick(function () {
that.placeholder.width = that.$refs.figure.clientWidth;
that.placeholder.height = Math.ceil(that.$refs.figure.clientWidth / that.aspectRatio);
})
}
}
},
mounted() {
const self = this;
self.calculateHeight();
self.$Lazyload.$on('loaded', () => {
self.showPlaceholder = false;
self.showError = false;
})
self.$Lazyload.$on('error', () => {
self.showPlaceholder = true;
self.showError = true;
})
}
}
</script>
<static-query>
query {
metadata {
sanityOptions{
projectId
dataset
}
}
}
</static-query>
Sist oppdatert 04.08.21