En komplet guide til JavaScript ResizeObserver API'et til at skabe responsive komponenter og håndtere dynamiske layouts med høj ydeevne.
ResizeObserver API: Det moderne webs hemmelighed til ubesværet sporing af elementstørrelse og responsive layouts
I den moderne webudviklings verden bygger vi applikationer med komponenter. Vi tænker i selvstændige, genanvendelige UI-blokke – kort, dashboards, widgets og sidebars. Alligevel har vores primære værktøj til responsivt design, CSS media queries, i årevis været fundamentalt afkoblet fra denne komponentbaserede virkelighed. Media queries bekymrer sig kun om én ting: størrelsen på det globale viewport. Denne begrænsning har tvunget udviklere op i et hjørne, hvilket har ført til komplekse beregninger, skrøbelige layouts og ineffektive JavaScript-hacks.
Hvad nu hvis en komponent kunne være bevidst om sin egen størrelse? Hvad nu hvis den kunne tilpasse sit layout, ikke fordi browservinduet blev ændret i størrelse, men fordi den container, den befinder sig i, blev klemt af et naboelement? Dette er problemet, som ResizeObserver API'et elegant løser. Det giver en ydedygtig, pålidelig og indbygget browsermekanisme til at reagere på ændringer i størrelsen af ethvert DOM-element, hvilket indvarsler en æra af ægte responsivitet på elementniveau.
Denne omfattende guide vil udforske ResizeObserver API'et fra bunden. Vi vil dække, hvad det er, hvorfor det er en monumental forbedring i forhold til tidligere metoder, og hvordan man bruger det gennem praktiske, virkelighedsnære eksempler. Ved slutningen vil du være rustet til at bygge mere robuste, modulære og dynamiske layouts end nogensinde før.
Den gamle metode: Begrænsningerne ved viewport-baseret responsivitet
For fuldt ud at værdsætte styrken ved ResizeObserver, må vi først forstå de udfordringer, det overvinder. I over et årti har vores responsive værktøjskasse været domineret af to tilgange: CSS media queries og JavaScript-baseret hændelseslytning.
CSS Media Queries' spændetrøje
CSS media queries er en hjørnesten i responsivt webdesign. De giver os mulighed for at anvende forskellige stilarter baseret på enhedens egenskaber, oftest viewportens bredde og højde.
Et typisk media query ser sådan ud:
/* Hvis browservinduet er 600px bredt eller mindre, gøres body'ens baggrundsfarve lyseblå */
@media screen and (max-width: 600px) {
body {
background-color: lightblue;
}
}
Dette fungerer fantastisk til justeringer af sidelayout på højt niveau. Men overvej en genanvendelig `UserInfo`-kortkomponent. Du vil måske have, at dette kort viser en avatar ved siden af brugerens navn i et bredt layout, men stabler avataren oven på navnet i et smalt layout. Hvis dette kort placeres i et bredt hovedindholdsområde, bør det bruge det brede layout. Hvis nøjagtig det samme kort placeres i en smal sidebar, bør det automatisk anvende det smalle layout, uanset den samlede viewport-bredde.
Med media queries er dette umuligt. Kortet har ingen viden om sin egen kontekst. Dets styling dikteres udelukkende af det globale viewport. Dette tvinger udviklere til at oprette variantklasser som `.user-card--narrow` og manuelt anvende dem, hvilket bryder komponentens selvstændige natur.
Ydeevnefælderne ved JavaScript-hacks
Det naturlige næste skridt for udviklere, der stod over for dette problem, var at vende sig mod JavaScript. Den mest almindelige tilgang var at lytte til `window`'s `resize`-hændelse.
window.addEventListener('resize', () => {
// For hver komponent på siden, der skal være responsiv...
// Hent dens aktuelle bredde
// Tjek om den overskrider en tærskel
// Anvend en klasse eller skift stilarter
});
Denne tilgang har flere kritiske fejl:
- Ydelsesmæssigt mareridt: `resize`-hændelsen kan affyres dusinvis eller endda hundredvis af gange under en enkelt træk-og-slip-størrelsesændring. Hvis din handler-funktion udfører komplekse beregninger eller DOM-manipulationer for flere elementer, kan du let forårsage alvorlige ydeevneproblemer, hakken (jank) og layout thrashing.
- Stadig afhængig af viewport: Hændelsen er bundet til `window`-objektet, ikke selve elementet. Din komponent ændrer sig stadig kun, når hele vinduet ændrer størrelse, ikke når dens forældrecontainer ændrer sig af andre årsager (f.eks. et søskendeelement, der tilføjes, en harmonika, der udvides, osv.).
- Ineffektiv polling: For at fange størrelsesændringer, der ikke skyldes en vinduesændring, tyede udviklere til `setInterval`- eller `requestAnimationFrame`-løkker for periodisk at tjekke et elements dimensioner. Dette er højst ineffektivt, da det konstant bruger CPU-cyklusser og dræner batteriet på mobile enheder, selv når intet ændrer sig.
Disse metoder var lappeløsninger, ikke rigtige løsninger. Nettet havde brug for en bedre måde – et effektivt, elementfokuseret API til at observere størrelsesændringer. Og det er præcis, hvad ResizeObserver leverer.
Ind træder ResizeObserver: En moderne, ydedygtig løsning
Hvad er ResizeObserver API'et?
ResizeObserver API'et er en browser-grænseflade, der giver dig besked, når et elements indholds- eller border-boks-størrelse ændres. Det giver en asynkron, ydedygtig måde at overvåge elementer for størrelsesændringer uden ulemperne ved manuel polling eller `window.resize`-hændelsen.
Tænk på det som en `IntersectionObserver` for dimensioner. I stedet for at fortælle dig, hvornår et element ruller ind i synsfeltet, fortæller det dig, hvornår dets boksstørrelse er blevet ændret. Dette kan ske af mange årsager:
- Browservinduet ændres i størrelse.
- Indhold tilføjes til eller fjernes fra elementet (f.eks. tekst, der ombrydes til en ny linje).
- Elementets CSS-egenskaber som `width`, `height`, `padding` eller `font-size` ændres.
- Et elements forælder ændrer størrelse, hvilket får det til at skrumpe eller vokse.
Væsentlige fordele i forhold til traditionelle metoder
ResizeObserver er ikke bare en mindre forbedring; det er et paradigmeskift for layout-håndtering på komponentniveau.
- Høj ydeevne: API'et er optimeret af browseren. Det affyrer ikke et callback for hver eneste pixelændring. I stedet samler det notifikationer og leverer dem effektivt inden for browserens renderingscyklus (typisk lige før paint), hvilket forhindrer den layout thrashing, der plager `window.resize`-handlere.
- Elementspecifik: Dette er dets superkraft. Du observerer et specifikt element, og callback'et affyres kun, når dette elements størrelse ændres. Dette afkobler din komponents logik fra det globale viewport, hvilket muliggør ægte modularitet og konceptet "Element Queries."
- Simpelt og deklarativt: API'et er bemærkelsesværdigt let at bruge. Du opretter en observer, fortæller den, hvilke elementer den skal overvåge, og angiver en enkelt callback-funktion til at håndtere alle notifikationer.
- Nøjagtigt og omfattende: Observeren giver detaljerede oplysninger om den nye størrelse, herunder content box, border box og padding, hvilket giver dig præcis kontrol over din layoutlogik.
Sådan bruges ResizeObserver: En praktisk guide
Brug af API'et involverer tre enkle trin: at oprette en observer, at observere et eller flere målelementer og at definere callback-logikken. Lad os bryde det ned.
Den grundlæggende syntaks
Kernen i API'et er `ResizeObserver`-konstruktøren og dens instansmetoder.
// 1. Vælg det element, du vil overvåge
const myElement = document.querySelector('.my-component');
// 2. Definer callback-funktionen, der skal køre, når en størrelsesændring registreres
const observerCallback = (entries) => {
for (let entry of entries) {
// 'entry'-objektet indeholder information om det observerede elements nye størrelse
console.log('Elementets størrelse er ændret!');
console.log('Målelement:', entry.target);
console.log('Ny content rect:', entry.contentRect);
console.log('Ny border box-størrelse:', entry.borderBoxSize[0]);
}
};
// 3. Opret en ny ResizeObserver-instans, og giv den callback-funktionen
const observer = new ResizeObserver(observerCallback);
// 4. Begynd at observere målelementet
observer.observe(myElement);
// For at stoppe med at observere et specifikt element senere:
// observer.unobserve(myElement);
// For at stoppe med at observere alle elementer bundet til denne observer:
// observer.disconnect();
Forståelse af callback-funktionen og dens entries
Callback-funktionen, du angiver, er kernen i din logik. Den modtager et array af `ResizeObserverEntry`-objekter. Det er et array, fordi observeren kan levere notifikationer for flere observerede elementer i en enkelt batch.
Hvert `entry`-objekt indeholder værdifuld information:
entry.target
: En reference til det DOM-element, der ændrede størrelse.entry.contentRect
: Et `DOMRectReadOnly`-objekt, der giver dimensionerne på elementets content box (width, height, x, y, top, right, bottom, left). Dette er en ældre egenskab, og det anbefales generelt at bruge de nyere boksstørrelsesegenskaber nedenfor.entry.borderBoxSize
: Et array, der indeholder et objekt med `inlineSize` (bredde) og `blockSize` (højde) på elementets border box. Dette er den mest pålidelige og fremtidssikrede måde at få et elements samlede størrelse på. Det er et array for at understøtte fremtidige brugstilfælde som f.eks. flerkolonnelayouts, hvor et element kan blive opdelt i flere fragmenter. Indtil videre kan du næsten altid trygt bruge det første element: `entry.borderBoxSize[0]`.entry.contentBoxSize
: Ligner `borderBoxSize`, men giver dimensionerne på content box (inden for padding).entry.devicePixelContentBoxSize
: Giver content box-størrelsen i enhedspixels.
En vigtig bedste praksis: Foretræk `borderBoxSize` og `contentBoxSize` frem for `contentRect`. De er mere robuste, stemmer overens med moderne CSS logiske egenskaber (`inlineSize` for bredde, `blockSize` for højde) og er vejen frem for API'et.
Brugsscenarier og eksempler fra den virkelige verden
Teori er godt, men ResizeObserver skinner virkelig, når man ser det i aktion. Lad os udforske nogle almindelige scenarier, hvor det giver en ren og kraftfuld løsning.
1. Dynamiske komponentlayouts ("Kort"-eksemplet)
Lad os løse problemet med `UserInfo`-kortet, som vi diskuterede tidligere. Vi ønsker, at kortet skifter fra et horisontalt til et vertikalt layout, når det bliver for smalt.
HTML:
<div class="card-container">
<div class="user-card">
<img src="avatar.jpg" alt="Brugeravatar" class="user-card-avatar">
<div class="user-card-info">
<h3>Jane Doe</h3>
<p>Senior Frontend-udvikler</p>
</div>
</div>
</div>
CSS:
.user-card {
display: flex;
align-items: center;
padding: 1rem;
border: 1px solid #ccc;
border-radius: 8px;
transition: all 0.3s ease;
}
/* Vertikal layout-tilstand */
.user-card.is-narrow {
flex-direction: column;
text-align: center;
}
.user-card-avatar {
width: 80px;
height: 80px;
border-radius: 50%;
margin-right: 1rem;
}
.user-card.is-narrow .user-card-avatar {
margin-right: 0;
margin-bottom: 1rem;
}
JavaScript med ResizeObserver:
const card = document.querySelector('.user-card');
const cardObserver = new ResizeObserver(entries => {
for (let entry of entries) {
const { inlineSize } = entry.borderBoxSize[0];
// Hvis kortets bredde er mindre end 350px, tilføjes klassen 'is-narrow'
if (inlineSize < 350) {
entry.target.classList.add('is-narrow');
} else {
entry.target.classList.remove('is-narrow');
}
}
});
cardObserver.observe(card);
Nu er det ligegyldigt, hvor dette kort placeres. Hvis du placerer det i en bred container, vil det være horisontalt. Hvis du trækker containeren mindre, vil `ResizeObserver` registrere ændringen og automatisk anvende `.is-narrow`-klassen, hvilket får indholdet til at flyde om. Dette er sand komponent-indkapsling.
2. Responsive datavisualiseringer og diagrammer
Datavisualiseringsbiblioteker som D3.js, Chart.js eller ECharts skal ofte gentegne sig selv, når deres container-element ændrer størrelse. Dette er et perfekt brugsscenarie for `ResizeObserver`.
const chartContainer = document.getElementById('chart-container');
// Antag, at 'myChart' er en instans af et diagram fra et bibliotek
// med en 'redraw(width, height)'-metode.
const myChart = createMyChart(chartContainer);
const chartObserver = new ResizeObserver(entries => {
const entry = entries[0];
const { inlineSize, blockSize } = entry.contentBoxSize[0];
// Debouncing er ofte en god idé her for at undgå at gentegne for ofte
// selvom ResizeObserver allerede samler kald.
requestAnimationFrame(() => {
myChart.redraw(inlineSize, blockSize);
});
});
chartObserver.observe(chartContainer);
Denne kode sikrer, at uanset hvordan `chart-container` ændres i størrelse – via et dashboards split-rude, en sammenklappelig sidebar eller en vinduesændring – vil diagrammet altid blive gen-renderet, så det passer perfekt inden for sine rammer, uden nogen ydeevnedræbende `window.onresize`-lyttere.
3. Adaptiv typografi
Nogle gange ønsker man, at en overskrift skal fylde en bestemt mængde horisontal plads, hvor dens skriftstørrelse tilpasser sig containerens bredde. Selvom CSS nu har `clamp()` og container query-enheder til dette, giver `ResizeObserver` dig finkornet JavaScript-kontrol.
const adaptiveHeading = document.querySelector('.adaptive-heading');
const headingObserver = new ResizeObserver(entries => {
const entry = entries[0];
const containerWidth = entry.borderBoxSize[0].inlineSize;
// En simpel formel til at beregne skriftstørrelse.
// Du kan gøre den så kompleks, som du har brug for.
const newFontSize = Math.max(16, containerWidth / 10);
entry.target.style.fontSize = `${newFontSize}px`;
});
headingObserver.observe(adaptiveHeading);
4. Håndtering af afkortning og "Læs mere"-links
Et almindeligt UI-mønster er at vise et tekststykke og en "Læs mere"-knap, kun hvis den fulde tekst flyder over sin container. Dette afhænger af både containerens størrelse og indholdets længde.
const textBox = document.querySelector('.truncatable-text');
const textContent = textBox.querySelector('p');
const truncationObserver = new ResizeObserver(entries => {
const entry = entries[0];
const target = entry.target;
// Tjek om scroll-højden er større end klient-højden
const isOverflowing = target.scrollHeight > target.clientHeight;
target.classList.toggle('is-overflowing', isOverflowing);
});
truncationObserver.observe(textContent);
Din CSS kan derefter bruge `.is-overflowing`-klassen til at vise en gradient-udtoning og "Læs mere"-knappen. Observeren sikrer, at denne logik kører automatisk, hver gang containerstørrelsen ændres, og viser eller skjuler knappen korrekt.
Overvejelser om ydeevne og bedste praksis
Selvom `ResizeObserver` er designet til at være højt ydende, er der et par bedste praksis og potentielle faldgruber, man skal være opmærksom på.
Undgåelse af uendelige løkker
Den mest almindelige fejl er at ændre en egenskab på det observerede element inde i callback'et, som igen forårsager endnu en størrelsesændring. Hvis du for eksempel tilføjer padding til elementet, vil dets størrelse ændre sig, hvilket vil udløse callback'et igen, som tilføjer mere padding, og så videre.
// FARE: Uendelig løkke!
const badObserver = new ResizeObserver(entries => {
const el = entries[0].target;
// Ændring af padding ændrer elementets størrelse, hvilket udløser observeren igen.
el.style.paddingLeft = parseInt(el.style.paddingLeft || 0) + 1 + 'px';
});
Browsere er smarte og vil opdage dette. Efter et par hurtige callbacks i samme frame vil de stoppe og kaste en fejl: `ResizeObserver loop limit exceeded`.
Sådan undgår du det:
- Tjek før du ændrer: Før du foretager en ændring, skal du tjekke, om den rent faktisk er nødvendig. I vores kort-eksempel tilføjer/fjerner vi for eksempel kun en klasse, vi ændrer ikke kontinuerligt en bredde-egenskab.
- Rediger et barnelement: Hvis muligt, hav observeren på en forældre-wrapper og foretag størrelsesændringer på et barnelement. Dette bryder løkken, da det observerede element selv ikke bliver ændret.
- Brug `requestAnimationFrame`:** I nogle komplekse tilfælde kan det at pakke din DOM-ændring ind i `requestAnimationFrame` udskyde ændringen til næste frame og dermed bryde løkken.
Hvornår man skal bruge `unobserve()` og `disconnect()`
Ligesom med `addEventListener` er det afgørende at rydde op i dine observers for at forhindre hukommelseslækager, især i Single-Page Applications (SPA'er) bygget med frameworks som React, Vue eller Angular.
Når en komponent afmonteres eller ødelægges, bør du kalde `observer.unobserve(element)` eller `observer.disconnect()`, hvis observeren slet ikke er nødvendig længere. I React gøres dette typisk i oprydningsfunktionen i et `useEffect`-hook. I Angular ville du bruge `ngOnDestroy`-livscyklus-hooket.
Browserunderstøttelse
I dag understøttes `ResizeObserver` i alle større moderne browsere, herunder Chrome, Firefox, Safari og Edge. Understøttelsen er fremragende for et globalt publikum. For projekter, der kræver understøttelse af meget gamle browsere som Internet Explorer 11, kan en polyfill bruges, men for de fleste nye projekter kan du trygt bruge API'et direkte.
ResizeObserver vs. fremtiden: CSS Container Queries
Det er umuligt at diskutere `ResizeObserver` uden at nævne dets deklarative modstykke: CSS Container Queries. Container Queries (`@container`) giver dig mulighed for at skrive CSS-regler, der gælder for et element baseret på størrelsen af dets forældrecontainer, ikke viewporten.
For vores kort-eksempel kunne CSS'en se sådan ud med Container Queries:
.card-container {
container-type: inline-size;
}
/* Selve kortet er ikke containeren, det er dens forælder */
.user-card {
display: flex;
/* ... andre stilarter ... */
}
@container (max-width: 349px) {
.user-card {
flex-direction: column;
}
}
Dette opnår det samme visuelle resultat som vores `ResizeObserver`-eksempel, men udelukkende i CSS. Så, gør dette `ResizeObserver` forældet? Absolut ikke.
Tænk på dem som komplementære værktøjer til forskellige opgaver:
- Brug CSS Container Queries, når du skal ændre stylingen af et element baseret på dets containers størrelse. Dette bør være dit standardvalg for rent præsentationsmæssige ændringer.
- Brug ResizeObserver, når du skal køre JavaScript-logik som reaktion på en størrelsesændring. Dette er essentielt for opgaver, som CSS ikke kan håndtere, såsom:
- At udløse en gen-rendering i et diagram-bibliotek.
- At udføre komplekse DOM-manipulationer.
- At beregne elementpositioner for en brugerdefineret layout-motor.
- At interagere med andre API'er baseret på et elements størrelse.
De løser det samme kerneproblem fra forskellige vinkler. `ResizeObserver` er det imperative, programmatiske API, mens Container Queries er den deklarative, CSS-indfødte løsning.
Konklusion: Omfavn elementbevidst design
`ResizeObserver` API'et er en fundamental byggesten for det moderne, komponentdrevne web. Det frigør os fra viewportens begrænsninger og giver os mulighed for at bygge ægte modulære, selvbevidste komponenter, der kan tilpasse sig ethvert miljø, de placeres i. Ved at levere en ydedygtig og pålidelig måde at overvåge elementdimensioner på, eliminerer det behovet for skrøbelige og ineffektive JavaScript-hacks, der har plaget frontend-udvikling i årevis.
Uanset om du bygger et komplekst data-dashboard, et fleksibelt designsystem eller blot en enkelt genanvendelig widget, giver `ResizeObserver` dig den præcise kontrol, du har brug for til at håndtere dynamiske layouts med selvtillid og effektivitet. Det er et kraftfuldt værktøj, der, når det kombineres med moderne layoutteknikker og de kommende CSS Container Queries, muliggør en mere modstandsdygtig, vedligeholdelsesvenlig og sofistikeret tilgang til responsivt design. Det er på tide at stoppe med kun at tænke på siden og begynde at bygge komponenter, der forstår deres eget rum.