css og layoutsanimasjoner

Bruke CSS Houdini: Lag tilpassede effekter som ikke er tilgjengelige i vanlig CSS

Bruke CSS Houdini: Lag tilpassede effekter som ikke er tilgjengelige i vanlig CSS

Prøv å gjøre noe som ser tilsynelatende enkelt ut: animer vinkelen i conic-gradient() jevnt ved hover. Du definerer en custom property --kat: 0deg, legger til transition: --kat 0.4s, holder musepekeren over – og ingenting skjer. Gradienten hopper til den nye verdien, som om transition ikke fantes i det hele tatt. Dette er ikke en feil i koden din. For nettleseren er --kat bare en vanlig, ugjennomsiktig streng – den vet ikke at det er en vinkel, og har derfor ingen måte å interpolere den mellom 0deg og 180deg på. Nettleseren kan interpolere tall, farger og lengder – fordi den kjenner typene deres. Custom properties, i motsetning til CSS' innebygde egenskaper, har ingen type i det hele tatt. Det er nettopp i dette gapet CSS Houdini hører hjemme.

Hva Houdini egentlig er

Dette er en vanlig misforståelse: Houdini er verken én teknologi eller én spesifikasjon. Det er en paraply for flere uavhengige forslag i W3C, forent av én idé – å eksponere deler av rendringsmotoren (verditolkning, layout, tegning, animasjon) som tidligere var fullstendig skjult for utvikleren. I stedet for å vente på at CSS skal få en ferdig egenskap som gjør akkurat det du trenger, får du en lavnivå-krok inn i et konkret trinn i denne prosessen.

Disse krokene realiseres som worklets – små JavaScript-moduler, konseptuelt beslektet med Web Workers, men designet spesifikt for rendringspipelinen. En worklet kjører utenfor hovedtråden, har ikke tilgang til window, document eller DOM, og kommuniserer kun gjennom en strengt definert kontrakt (f.eks. paint(ctx, size, properties)). Dette er ikke en begrensning som følge av latskap fra spesifikasjonsforfatterne – det er en bevisst arkitektonisk beslutning. Takket være isolasjonen kan motoren kjøre worklets parallelt, på separate tråder, og cache resultatet uten risiko for at koden din rører ved noe den ikke skal. Den samme isolasjonen er grunnen til at du ikke finner Date, performance.now() eller fetch inne i en worklet – begrensningen på presis tid og nettverkstilgang er en bevisst beskyttelse mot timing-/fingerprinting-angrep fra rendringskoden på siden.

De ulike delene av denne paraplyen befinner seg i dag på helt forskjellige stadier: én har lenge vært en del av Baseline, en annen fungerer bare i Chrome, og enda en annen er fortsatt eksperimentell etter et tiår. Dette skillet er avgjørende – og de fleste introduksjonsartikler om Houdini hopper over det, og behandler hele greia som én, ferdig-til-bruk teknologi.

Den eneste delen av Houdini du virkelig bør bruke i dag: @property

CSS Properties and Values API løser akkurat det problemet artikkelen åpnet med. Du registrerer en custom property med en konkret type (syntax), en startverdi og informasjon om hvorvidt den skal arves – og fra det øyeblikket behandler nettleseren den som en fullverdig, animerbar verdi, ikke en streng.

html

<div class="ring"></div>

css

@property --kat {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
.ring {
width: 200px;
height: 200px;
border-radius: 50%;
background: conic-gradient(from var(--kat), #ff4d4d, #4d79ff, #4dff88, #ff4d4d);
transition: --kat 0.6s ease;
}
.ring:hover {
--kat: 180deg;
}

Dette eksemplet gjør faktisk noe vanlig CSS ikke klarer – uten @property ville transition ovenfor rett og slett ikke ha fungert, akkurat som i scenarioet innledningsvis. syntax: '<angle>' forteller nettleseren at --kat er en vinkel, slik at den kan interpoleres jevnt; inherits: false forhindrer at underelementer arver verdien ved et uhell, noe som ved custom properties ofte er en kilde til feil som er vanskelige å spore opp.

Samme effekt kan oppnås fra JavaScript, noe som gir mening når du registrerer egenskaper dynamisk (f.eks. generert ut fra data) i stedet for hardkodet i stilarket:

javascript

if ('registerProperty' in CSS) {
CSS.registerProperty({
name: '--kat',
syntax: '<angle>',
inherits: false,
initialValue: '0deg',
});
}

Dette er den eneste delen av Houdini som i dag har Baseline-status – den fungerer nativt i Chrome, Firefox (fra versjon 128) og Safari (fra 16.4), uten polyfills og uten @supports. Hvis du bare skal huske og implementere én ting fra hele Houdini, er det denne.

CSS Paint API: worklet-en som faktisk tegner

Paint API lar deg erstatte background med en paint()-funksjon som tegner direkte på elementet, ved hjelp av et forenklet grensesnitt som minner om Canvas 2D (PaintRenderingContext2D) – uten fillText, drawImage eller pikselavlesing. Det som skiller denne tilnærmingen fra vanlig tegning på <canvas>, er inputProperties: worklet-en deklarerer hvilke custom properties den vil observere, og tegnes automatisk på nytt kun når en av dem endres – uten en eneste linje JavaScript dedikert til å lytte etter hendelser.

Under ser du et mønster av diagonale advarselsstriper, der vinkelen styres av den samme, tidligere registrerte egenskapen --kat (nærmere bestemt dens lokale variant --stripe-angle) – noe som viser hvordan @property og Paint API naturlig utfyller hverandre.

html

<div class="hazard"></div>

css

@property --stripe-angle {
syntax: '<angle>';
inherits: false;
initial-value: 45deg;
}
.hazard {
width: 100%;
height: 120px;
--stripe-angle: 45deg;
background: paint(hazardStripes);
transition: --stripe-angle 0.4s ease;
}
.hazard:hover {
--stripe-angle: 135deg;
}

javascript

// main.js
if ('paintWorklet' in CSS) {
CSS.paintWorklet.addModule('hazard-stripes.js');
}

javascript

// hazard-stripes.js
class HazardStripesPainter {
static get inputProperties() {
return ['--stripe-angle'];
}
paint(ctx, size, properties) {
const angle = properties.get('--stripe-angle').to('deg').value;
const stripeWidth = 24;
const { width, height } = size;
const diagonal = Math.sqrt(width ** 2 + height ** 2) * 2;
ctx.save();
ctx.translate(width / 2, height / 2);
ctx.rotate((angle * Math.PI) / 180);
ctx.translate(-diagonal, -diagonal);
let stripeIndex = 0;
for (let x = 0; x < diagonal * 2; x += stripeWidth) {
ctx.fillStyle = stripeIndex % 2 === 0 ? '#111111' : '#f5c400';
ctx.fillRect(x, 0, stripeWidth, diagonal * 2);
stripeIndex++;
}
ctx.restore();
}
}
registerPaint('hazardStripes', HazardStripesPainter);

Legg merke til properties.get('--stripe-angle').to('deg').value – dette er CSS Typed OM, nok et medlem av samme API-familie. I stedet for å parse strengen manuelt (parseFloat, trimme bort "deg"), får du en typet verdi, CSSUnitValue, som du eksplisitt konverterer til den enheten du trenger. Fordi vinkelen på de tegnede stripene utelukkende avhenger av custom property-en som er deklarert i inputProperties, vet motoren nøyaktig når elementet må tegnes på nytt – en endring i en urelatert CSS-egenskap et annet sted på siden vil ikke utløse paint() på nytt.

Layout API og Animation Worklet: virkeligheten i 2026

Her begynner den delen de fleste introduksjonsartikler om Houdini tier om. CSS Layout API – den tredje pilaren i samme spesifikasjon, som lar deg definere din egen layout-algoritme (tilsvarende din egen display: grid) – har eksistert som utkast siden 2015 og er fortsatt eksperimentell etter et tiår. Ingen stabil nettleser har implementert den nativt; den er kun tilgjengelig bak flagg eller i Chromes origin trials. Hvis du kommer over en artikkel som viser display: layout(navn) som en ferdig løsning, bør du sjekke publiseringsdatoen – det er trolig en demo fra en origin trial for flere år siden, ikke noe du kan kjøre i brukerens nettleser i dag.

Animation Worklet skulle gi full kontroll over scroll-drevne animasjoner, utført utenfor hovedtråden. I praksis er det bare Chromium som støtter den. Enda viktigere – hovedbruksområdet er ikke lenger en grunn til å gripe til Houdini, fordi CSS har fått en nativ løsning på det samme problemet: animation-timeline: scroll() lar deg i dag koble en animasjon til scrolling uten en eneste linje JavaScript og uten worklets, med økende nettleserstøtte. Dette illustrerer godt et bredere mønster: noen av grunnene til at Houdini i det hele tatt oppsto, har med tiden havnet rett i CSS-spesifikasjonen, i stedet for å forbli et lavnivå-API for utviklere.

Når det gir mening i praksis

  • Bruk @property fritt, uten sikringstiltak – den har Baseline-status og trenger verken @supports eller polyfill.
  • Behandle Paint API som progressiv forbedring – pakk den inn i @supports(background: paint(x)) med en fornuftig fallback i ren CSS (f.eks. en vanlig gradient) for Firefox, eller bruk polyfillen css-paint-polyfill hvis effekten må fungere overalt.
  • Ikke planlegg en produksjonsfunksjon rundt Layout API – den er fortsatt et eksperiment uten reell nettleserstøtte, fin å leke med, men ikke noe for veikartet.
  • Husk at paint() ikke er «gratis» CSS – det er din egen JavaScript-kode, som kjøres på nytt hver gang en deklarert inputProperties endres. Kostbare beregninger (støygenerering, komplekse prosedyremønstre) må optimaliseres akkurat som en rendringsløkke på <canvas> – det er lett å ved et uhell bygge en animasjon som tegner hele elementet på nytt bilde for bilde på selve hovedtråden for layout.

Oppsummering

Houdini er ikke én beslutning om «å bruke eller ikke bruke» – det er tre, fire separate beslutninger med ulik risiko. For det første løser @property et reelt, konkret problem (animering av custom properties) og er i dag like trygt å bruke som enhver annen CSS-egenskap – dette er den eneste delen av økosystemet du bør implementere uten å nøle. For det andre gir Paint API reell kraft (en worklet som bare tegnes på nytt når de deklarerte avhengighetene endres, uten manuell hendelseslytting), men krever en bevisst plan for nettlesere uten støtte – dette er et verktøy for progressiv forbedring, ikke for kritiske funksjoner. For det tredje er Layout API og Animation Worklet gode eksempler på at en spesifikasjon i seg selv ikke garanterer adopsjon – etter mange år venter den ene fortsatt på implementering, mens den andre delvis er blitt erstattet av enklere, nativ CSS. Før du griper til Houdini, bør du sjekke hvilken av disse tre kurvene den delen du trenger, faktisk hører hjemme i.