Varför CLS fortfarande saboterar användarupplevelsen
Cumulative Layout Shift (CLS) mäter den visuella stabiliteten på din webbsida — alltså hur mycket synligt innehåll hoppar runt medan sidan laddas och används. Det är det tredje och sista Core Web Vital-måttet, tillsammans med LCP och INP, och ärligt talat är det kanske det mest irriterande för slutanvändare. Du har garanterat upplevt det själv: du ska precis klicka på en länk och plötsligt skiftar hela sidan nedåt så att du istället trycker på en annons. Eller texten du läser hoppar iväg för att en bild ovanför laddas in utan reserverat utrymme. Otroligt frustrerande.
Google kräver att CLS ska vara under 0,1 för minst 75 procent av alla sidladdningar för att klassas som "bra". Allt över 0,25 klassas som dåligt. Enligt CrUX-data från slutet av 2025 klarar ungefär 80 procent av webbplatser CLS-tröskelvärdet — det är faktiskt det Core Web Vital som flest sajter klarar. Men det betyder inte att du kan ignorera det.
De sajter som inte klarar sig har ofta riktigt allvarliga problem som direkt påverkar konverteringsgrad och SEO-ranking.
Den här guiden tar dig igenom allt du behöver veta för att identifiera, mäta och eliminera layoutskiftningar — med konkreta kodexempel och tekniker som faktiskt fungerar i praktiken under 2026.
Hur CLS beräknas: sessionsfönster och största burst
Innan du börjar optimera behöver du förstå exakt hur CLS-poängen räknas ut. Det har förändrats sedan måttet introducerades, och överraskande många utvecklare har fortfarande en föråldrad bild av hur det fungerar.
Inte längre summan av alla skiftningar
Tidigare beräknades CLS som summan av alla layoutskiftningar under hela sidans livslängd. Det straffade ensidiga applikationer (SPA:er) och långlivade sidor orättvist, eftersom skiftningar ackumulerades utan tak. Sedan 2022 används istället metoden med sessionsfönster — en betydligt rättvisare modell.
Sessionsfönster och "largest burst"
En layoutskiftning grupperas i sessionsfönster — kluster av skiftningar som inträffar med mindre än 1 sekunds mellanrum, upp till maximalt 5 sekunder per fönster. Ditt CLS-värde är poängen för det sessionsfönster som har den högsta totala skiftningspoängen. Alla andra fönster ignoreras helt.
Det innebär att en enstaka stor burst av skiftningar under sidladdning kan ge ett högt CLS-värde, även om resten av sessionen är helt stabil. Omvänt: om du sprider ut dina skiftningar med mer än en sekunds mellanrum hamnar de i separata fönster och varje enskilt fönster kan vara under gränsvärdet. Lite kontraintuitivt kanske, men så fungerar det.
Impact Fraction och Distance Fraction
Varje enskild layoutskiftning beräknas som produkten av två värden:
- Impact Fraction: Andelen av vyporten som påverkas av det instabila elementet mellan två renderingsrutor. Om ett element upptar 50 procent av vyportens höjd och ett annat element skjuts ner 25 procent, blir impact fraction 0,75 (unionen av båda områdena).
- Distance Fraction: Det största avståndet som något instabilt element förflyttats, dividerat med vyportens största dimension. Om ett element rör sig 300 pixlar i en vyport som är 800 pixlar hög, blir distance fraction 0,375.
Skiftningspoängen för den enskilda händelsen blir alltså: 0,75 × 0,375 = 0,28125. Det ensamma överstiger redan gränsvärdet 0,25 för "dåligt". Stora element som rör sig långt straffas hårt — och det med rätta.
Identifiera CLS-problem med rätt verktyg
Precis som med INP och LCP behöver du mäta innan du optimerar. Men CLS har en särskild utmaning: laboratorietester och fältdata kan ge väldigt olika resultat.
Varför labbvärden ofta underskattar CLS
Det här är något jag har sett otaliga gånger. Lighthouse och andra syntesverktyg mäter CLS bara under den initiala sidladdningen. I verkligheten fortsätter användare att scrolla, interagera och trigga dynamiskt innehåll — allt som kan orsaka ytterligare skiftningar. Annonser, lazy-laddade bilder och asynkrona widgets laddas dessutom annorlunda i labbtester jämfört med verklig användning.
Det är därför fältdata (CrUX, RUM) alltid ska vara din primära källa för CLS. Alltid.
Mät CLS med web-vitals-biblioteket
import { onCLS } from 'web-vitals/attribution';
onCLS((metric) => {
console.log('CLS:', metric.value);
console.log('Största skiftningselement:', metric.attribution.largestShiftTarget);
console.log('Största skiftningsvärde:', metric.attribution.largestShiftValue);
console.log('Största skiftningstid:', metric.attribution.largestShiftTime, 'ms');
console.log('Laddningstillstånd:', metric.attribution.loadState);
// Skicka till ditt analytiksystem
if (metric.value > 0.1) {
sendToAnalytics({
metric: 'CLS',
value: metric.value,
element: metric.attribution.largestShiftTarget,
loadState: metric.attribution.loadState
});
}
}, { reportAllChanges: true });
Attributeringen visar vilket element som bidrog mest till CLS och i vilken fas av sidladdningen det hände. Det är ovärderlig information för att rikta dina optimeringsinsatser rätt.
Chrome DevTools Layout Shift-regioner
I Chrome DevTools kan du aktivera "Layout Shift Regions" under Rendering-fliken. Det visar blåmarkerade områden varje gång en layoutskiftning inträffar, i realtid. Väldigt visuellt och intuitivt. Kombinera det med Performance-panelens "Experience"-sektion för att se exakt vilka element som skiftar och vad som utlöser dem.
// Övervaka layoutskiftningar i realtid
const clsObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
console.log('Layoutskiftning:', entry.value.toFixed(4));
entry.sources?.forEach((source) => {
console.log(' Element:', source.node?.nodeName, source.node?.className);
console.log(' Föregående position:', source.previousRect);
console.log(' Ny position:', source.currentRect);
});
}
}
});
clsObserver.observe({ type: 'layout-shift', buffered: true });
Bilder utan dimensioner: den vanligaste CLS-boven
Bilder utan explicita width- och height-attribut är fortfarande den absolut vanligaste orsaken till layoutskiftningar. Det är lite förvånande att det fortfarande händer år 2026, men det gör det. När webbläsaren stöter på en <img>-tagg utan dimensioner allokerar den initialt noll utrymme. När bilden sedan laddas in expanderar elementet till sin fulla storlek och skjuter allt nedanför nedåt.
Alltid ange width och height
<!-- Dåligt: inga dimensioner, orsakar CLS -->
<img src="/produktbild.webp" alt="Produkt">
<!-- Bra: explicita dimensioner reserverar utrymme -->
<img
src="/produktbild.webp"
alt="Produkt"
width="800"
height="600"
loading="lazy"
>
Moderna webbläsare använder width- och height-attributen för att beräkna bildens aspect ratio innan den laddats ner. Det innebär att utrymmet reserveras korrekt oavsett faktisk storlek efter CSS-skalning. Enkelt och effektivt.
CSS aspect-ratio för responsiva bilder
Om du har responsiva bilder som skalas med CSS kan du använda aspect-ratio-egenskapen som extra säkerhet:
img {
max-width: 100%;
height: auto;
aspect-ratio: attr(width) / attr(height);
}
/* Eller ange explicit aspect ratio */
.hero-image {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
}
Alla moderna webbläsare stöder aspect-ratio fullt ut sedan 2022, så det finns egentligen ingen anledning att inte använda det.
Typsnitt som orsakar layoutskiftningar: FOUT och FOIT
Webbtypsnitt är en underskattad CLS-källa — och en som många missar helt. När webbläsaren initialt renderar text med ett fallback-typsnitt och sedan byter till det laddade webbtypsnitt som har andra metriska egenskaper (andra teckenbredder, radhöjder och mellanrum), kan hela textblock skifta position. Det kallas FOUT (Flash of Unstyled Text) och det påverkar CLS direkt.
font-display: optional vs swap
Det finns två huvudstrategier för typsnittsladdning:
- font-display: swap: Visar fallback-typsnittet direkt och byter sedan till webtypsnittet när det laddats. Bra för LCP (texten syns omedelbart) men riskerar CLS om typsnitten har olika mått.
- font-display: optional: Ger webbläsaren ungefär 100 ms att ladda typsnittet. Om det inte hinner laddas används fallback-typsnittet för hela sidvisningen — inget byte, ingen skiftning. Typsnittet cachas för nästa besök.
Min rekommendation? Använd font-display: optional för brödtext, där CLS-påverkan är störst. För rubriktext och varumärkestypsnitt där det visuella uttrycket är viktigare kan du använda swap — men då behöver du metriska överstyrningar.
Metriska överstyrningar: eliminera CLS med font-display: swap
Med CSS-egenskaperna size-adjust, ascent-override, descent-override och line-gap-override kan du justera fallback-typsnittet så att det upptar exakt samma utrymme som webtypsnittet. Resultatet? Inget visuellt hopp vid bytet.
/* Metriskt justerat fallback-typsnitt */
@font-face {
font-family: "Inter Fallback";
src: local("Arial");
ascent-override: 90%;
descent-override: 22%;
line-gap-override: 0%;
size-adjust: 107%;
}
/* Webbtypsnitt med optimal laddningsstrategi */
@font-face {
font-family: "Inter";
src: url("/fonts/inter-latin.woff2") format("woff2");
font-display: optional;
font-weight: 100 900;
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC;
}
body {
font-family: "Inter", "Inter Fallback", system-ui, sans-serif;
}
Ramverk som Next.js och Nuxt.js genererar dessa överstyrningar automatiskt, vilket är riktigt smidigt. Om du inte använder ett sådant ramverk kan verktyg som Fontaine eller Capsize hjälpa dig beräkna rätt värden.
Preload kritiska typsnitt
<link
rel="preload"
href="/fonts/inter-latin.woff2"
as="font"
type="font/woff2"
crossorigin
>
Att preloada ditt primära typsnitt minskar risken att det inte hinner laddas inom det 100 ms-fönster som font-display: optional ger. Det ger dig det bästa av båda världar: webtypsnittet används på de allra flesta sidladdningar och CLS hålls på noll.
Annonser och dynamiskt innehåll: den svåraste CLS-utmaningen
Okej, låt oss prata om elefanten i rummet. Annonser, cookie-banners och dynamiskt injicerat innehåll är bland de svåraste CLS-bovarna att hantera — framför allt för att du inte alltid har full kontroll över dem. Annonsplatser serverar olika annonsstorlekar vid olika tidpunkter, och du vet helt enkelt inte i förväg vilken storlek som kommer att visas.
Reservera utrymme med min-height
Den mest effektiva strategin är att reservera utrymme för annonsplatser med CSS — och att använda min-height istället för height för att hantera variabla annonsstorlekar:
/* Reservera utrymme för annonsplats baserat på vanligaste storlekar */
.ad-slot-leaderboard {
min-height: 90px; /* Standard leaderboard-höjd */
width: 100%;
max-width: 728px;
background-color: #f5f5f5; /* Visuell platshållare */
contain: layout style;
}
.ad-slot-rectangle {
min-height: 250px; /* Medium rectangle */
width: 300px;
contain: layout style;
}
/* Kollapsa INTE tomma annonsplatser */
.ad-slot:empty {
/* Behåll min-height även utan annons */
display: block;
}
En viktig detalj som många missar: kollapsa aldrig tomma annonscontainrar. Om du tar bort utrymmet när ingen annons returneras orsakar det exakt samma CLS som om annonsen dyker upp och skjuter ner innehåll. Behåll platshållaren — alltid.
Placera dynamiskt innehåll under the fold
Layoutskiftningar som inträffar utanför vyportens synliga område påverkar inte CLS. Bra att veta, eller hur? Placera därför annonser, widgetar och annat dynamiskt innehåll under the fold när det är möjligt. Om du använder lazy loading för annonser, sätt en offset på minst 200 pixlar så att annonsen laddas innan den scrollas in i synligt område.
Cookie-banners som inte orsakar CLS
Cookie-banners är en klassisk CLS-källa om de implementeras som ett element som skjuts in överst på sidan och trycker ner allt annat. Lösningen är enkel: använd position: fixed eller position: sticky så att bannern överlagrar innehållet istället för att förskjuta det.
.cookie-banner {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 1000;
/* Bannern överlagrar — inget innehåll skiftas */
}
Iframes och inbäddningar: reservera rätt utrymme
YouTube-videor, Google Maps, sociala medieinbäddningar — alla dessa använder iframes och kan orsaka CLS om du inte reserverar utrymme korrekt. Inbäddningsplattformarna vet ofta inte i förväg hur stort innehållet blir, så ansvaret hamnar helt på dig.
Aspect ratio-tekniken för responsiva iframes
<!-- Responsiv iframe med aspect ratio 16:9 -->
<div class="iframe-wrapper">
<iframe
src="https://www.youtube.com/embed/VIDEO_ID"
width="560"
height="315"
loading="lazy"
title="Videoinnehåll"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope"
allowfullscreen
></iframe>
</div>
<style>
.iframe-wrapper {
position: relative;
aspect-ratio: 16 / 9;
width: 100%;
max-width: 560px;
}
.iframe-wrapper iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 0;
}
</style>
Genom att använda aspect-ratio direkt på wrapper-elementet får du en responsiv container som alltid har rätt proportioner, utan den äldre padding-bottom-hacken. Mycket renare lösning.
Fasad-mönstret: ladda inte inbäddningar förrän de behövs
En YouTube-inbäddning laddar hundratals kilobyte JavaScript redan vid sidladdning. Det är ganska vansinnigt om videon bara finns längre ner på sidan. Använd istället en statisk miniatyrbild som platshållare och ladda den riktiga inbäddningen först när användaren klickar:
<div class="video-facade" data-video-id="VIDEO_ID">
<img
src="https://i.ytimg.com/vi/VIDEO_ID/hqdefault.jpg"
alt="Klicka för att spela video"
width="560"
height="315"
loading="lazy"
>
<button aria-label="Spela video">▶</button>
</div>
document.querySelectorAll('.video-facade').forEach((facade) => {
facade.addEventListener('click', function() {
const videoId = this.dataset.videoId;
const iframe = document.createElement('iframe');
iframe.src = `https://www.youtube.com/embed/${videoId}?autoplay=1`;
iframe.width = 560;
iframe.height = 315;
iframe.allow = 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope';
iframe.allowFullscreen = true;
this.replaceWith(iframe);
}, { once: true });
});
Fasad-mönstret eliminerar CLS helt för inbäddningar eftersom platshållaren har exakt samma dimensioner som den slutliga iframen. Och som bonus: sidan laddar mycket snabbare.
CSS Containment och content-visibility: avancerade tekniker
Nu börjar vi komma in på de lite mer avancerade teknikerna. CSS Containment och content-visibility kan förbättra både renderingsprestanda och CLS — men de måste användas med omsorg. Fel konfiguration kan faktiskt göra saker värre.
CSS Containment: isolera layoutberäkningar
Egenskapen contain instruerar webbläsaren att isolera ett elements rendering från resten av sidan. Med contain: layout säger du till webbläsaren att inget inuti elementet kan påverka layouten utanför det — och vice versa. Det begränsar "spridningseffekten" av en layoutskiftning, vilket kan vara ovärderligt i vissa situationer.
/* Isolera annonscontainrar från övrig layout */
.ad-container {
contain: layout style;
}
/* Isolera dynamiska sektioner */
.dynamic-content-section {
contain: layout style paint;
}
/* Maximal containment för helt isolerade sektioner */
.isolated-widget {
contain: strict; /* Samma som: size layout style paint */
width: 300px;
height: 250px;
}
Fälttester visar att contain: layout style på annonscontainrar kan förhindra att en layoutskiftning i annonsen kaskaderar och påverkar element utanför containern. Det minskar inte själva skiftningen, men begränsar dess impact fraction.
content-visibility: auto — med varning
content-visibility: auto säger åt webbläsaren att hoppa över rendering av element som inte är synliga i vyporten. Det kan dramatiskt förbättra initial renderingsprestanda. Men — och det här är viktigt — det finns en hake: element med content-visibility: auto kollapsar till noll höjd om inte du också anger contain-intrinsic-size.
/* FARLIGT: orsakar CLS vid scroll */
.article-section {
content-visibility: auto;
/* Elementet kollapsar till 0px höjd */
}
/* KORREKT: med uppskattad storlek */
.article-section {
content-visibility: auto;
contain-intrinsic-size: auto 500px;
/* Webbläsaren reserverar 500px höjd */
/* "auto" gör att den kommer ihåg verklig storlek efter rendering */
}
Nyckelordet auto framför storleksvärdet gör att webbläsaren kommer ihåg den faktiska höjden efter att elementet renderats en gång. Det innebär att scrollpositioner bevaras korrekt vid tillbaka-navigering. Utan auto kan du få obehagliga hopp i scrollbaren — något som användarna definitivt märker.
Back/Forward Cache: gratis CLS-vinst
Bfcache (Back/Forward Cache) sparar sidans fullständiga renderingstillstånd i webbläsarens minne när användaren navigerar bort. Om användaren går tillbaka återställs sidan omedelbart — utan någon laddning och helt utan layoutskiftningar. Det är i princip gratis prestanda.
Det löser inte CLS vid den initiala sidladdningen, men det eliminerar alla skiftningar vid tillbaka-navigering. Med tanke på att en stor andel sidvisningar faktiskt är tillbaka- och framåt-navigeringar kan detta ha en märkbar effekt på ditt fält-CLS.
Säkerställ att dina sidor är bfcache-kompatibla
Vanliga orsaker till att sidor utesluts från bfcache:
Cache-Control: no-storepå HTML-svaret- Öppna WebSocket-anslutningar vid navigering
unload-eventlyssnare (användpagehideistället)- Pågående
BroadcastChannel-meddelanden
// Använd pagehide istället för unload
// DÅLIGT: blockerar bfcache
window.addEventListener('unload', () => {
sendAnalytics();
});
// BRA: kompatibelt med bfcache
window.addEventListener('pagehide', (event) => {
if (!event.persisted) {
sendAnalytics();
}
});
// Testa bfcache-kompatibilitet
window.addEventListener('pageshow', (event) => {
if (event.persisted) {
console.log('Sidan återställdes från bfcache');
}
});
Du kan testa bfcache-kompatibilitet i Chrome DevTools under Application > Back/forward cache. Det visar exakt varför en sida utesluts, om den gör det. Kolla det — det tar bara någon minut.
Skeletonscreens: bättre än spinners
Laddningsspinners reserverar sällan rätt mängd utrymme för det innehåll som kommer att visas. När det verkliga innehållet ersätter spinnern uppstår ofta en layoutskiftning. Skeletonscreens — platshållare som efterliknar det slutliga innehållets layout — löser det problemet på ett elegant sätt.
<!-- Skeleton som matchar artikelkortets layout -->
<div class="article-card skeleton">
<div class="skeleton-image" style="aspect-ratio: 16/9;"></div>
<div class="skeleton-title" style="height: 1.5em; width: 80%;"></div>
<div class="skeleton-text" style="height: 1em; width: 60%;"></div>
</div>
<style>
.skeleton-image,
.skeleton-title,
.skeleton-text {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
border-radius: 4px;
}
@keyframes shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
</style>
Det kritiska här är att skeletonscreenen har exakt samma dimensioner som det slutliga innehållet. Om skelettet är 200 pixlar högt men det verkliga innehållet blir 350 pixlar har du bara bytt ut en typ av CLS mot en annan. Lite pinsamt, men det händer oftare än man tror.
CSS-animationer som inte orsakar layoutskiftningar
Inte alla visuella förändringar är layoutskiftningar. CSS transform- och opacity-egenskaperna hanteras av GPU:ns compositor och triggar inte layout- eller paint-beräkningar — de orsakar alltså aldrig CLS.
/* DÅLIGT: top/left triggar layout */
.notification-enter {
position: relative;
animation: slideDown 0.3s ease;
}
@keyframes slideDown {
from { top: -100px; }
to { top: 0; }
}
/* BRA: transform triggar inte layout */
.notification-enter {
animation: slideDownTransform 0.3s ease;
}
@keyframes slideDownTransform {
from { transform: translateY(-100px); }
to { transform: translateY(0); }
}
Samma princip gäller för accordion-animationer, menyer och modaler. Använd alltid transform istället för top, left, width eller height för animationer. Det är bättre både för CLS och för allmän renderingsprestanda. Ett enkelt byte som gör stor skillnad.
En CLS-checklista för 2026
Så, låt oss sammanfatta. Här är en checklista du kan använda för varje ny sida eller funktion du bygger:
- Bilder: Ange alltid width och height. Använd aspect-ratio i CSS som extra säkerhet.
- Typsnitt: Använd font-display: optional för brödtext. Preloada kritiska typsnitt. Använd metriska överstyrningar om du behöver swap.
- Annonser: Reservera utrymme med min-height. Kollapsa aldrig tomma platser. Använd contain: layout style.
- Iframes: Ange dimensioner eller använd aspect-ratio-wrapper. Överväg fasad-mönstret.
- Dynamiskt innehåll: Injicera aldrig innehåll ovanför synligt innehåll. Använd position: fixed/sticky för banners.
- Animationer: Använd transform och opacity — aldrig top, left, width eller height.
- content-visibility: Ange alltid contain-intrinsic-size med auto-nyckelord.
- Bfcache: Undvik unload-lyssnare och Cache-Control: no-store.
- Monitorering: Sätt CLS-larm vid 0,08 (80 procent av Googles tröskelvärde). Använd fältdata, inte bara labbtester.
Vanliga frågor om CLS-optimering
Vad räknas som en bra CLS-poäng?
Google definierar tre nivåer: under 0,1 är "bra", mellan 0,1 och 0,25 "behöver förbättras", och över 0,25 är "dåligt". Sikta på att 75:e percentilen av dina sidladdningar ska vara under 0,1. Sätter du ditt övervakningslarm vid 0,08 har du lite marginal innan det börjar påverka ranking.
Påverkar CLS sökmotorranking direkt?
Ja, CLS är en av tre Core Web Vitals-mätvärden som Google använder som rankningssignal. När två sidor har liknande innehållsrelevans kommer den med bättre Core Web Vitals att rankas högre. Sajter som klarar alla tre tröskelvärden har i genomsnitt 24 procent lägre bounce rate — det är en ganska signifikant skillnad.
Varför skiljer sig mitt CLS-värde mellan Lighthouse och CrUX?
Lighthouse mäter CLS enbart under sidladdningen i en kontrollerad miljö. CrUX (Chrome User Experience Report) mäter CLS från verkliga användare under hela sidans livslängd — inklusive scrollning, interaktioner och dynamiskt laddade annonser. Fältdata från CrUX är det som Google använder för ranking, och det ger ofta högre CLS-värden. Så lita på CrUX-datan.
Räknas layoutskiftningar efter en användarinteraktion?
Layoutskiftningar som inträffar inom 500 millisekunder efter en användarinteraktion (klick, knapptryck) räknas inte mot CLS. Det innebär att en dropdown-meny som expanderar efter ett klick inte påverkar CLS. Men skiftningar som sker asynkront, även om de utlöstes indirekt av en interaktion, kan fortfarande räknas om de inträffar efter 500 ms-fönstret. Något att ha koll på.
Hur fixar jag CLS orsakad av annonser jag inte kontrollerar?
Reservera utrymme med min-height baserat på den vanligaste annonsstorleken i ditt nätverk. Använd CSS containment (contain: layout style) på annonsbehållaren för att isolera skiftningen. Placera annonser under the fold när det är möjligt, och ladda dem med en offset så de hinner renderas innan de scrollas in i synligt område. Det är inte en perfekt lösning, men det är det bästa du kan göra utan kontroll över själva annonsen.