Angi bildestørrelse for å unngå layout shift

Gi info til nettleseren om hvor stor plass bildet kommer til å ta

Laster inn bilde
Bilde av en dataskjerm som viser mange bilder

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.

Laster inn bilde
Skjermbilde av hvordan det ser ut før hovedbildet er lastet inn
Skjermbilde av hvordan det ser ut før hovedbildet er lastet 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

Dette innlegget er eldre enn 1 år. Hele eller deler av innholdet kan være utdatert eller ikke aktuelt lengre.

Helge Johnsen

Dette er mitt private nettsted. Jeg jobber til daglig som seniorkonsulent i et Norsk IT-selskap. I denne bloggen skriver jeg om store og små ting som rører seg i min private verden. Bloggen består stort sett av tips, egne prosjekter og tanker. Les mer om meg her.

Har du noen tanker eller kommentarer om dette blogginnlegget så finner du meg på en rekke sosiale medier.