Använda CSS Houdini: Skapa anpassade effekter som inte finns i vanlig CSS
Använda CSS Houdini: Skapa anpassade effekter som inte finns i vanlig CSS
Försök göra något som verkar enkelt: animera en vinkel i conic-gradient() mjukt vid hover. Du definierar en custom property --kat: 0deg, lägger till transition: --kat 0.4s, för musen över – och ingenting händer. Gradienten byter läge abrupt, som om transition inte fanns alls. Det är inte ett fel i din kod. För webbläsaren är --kat bara en vanlig, ogenomskinlig sträng – den vet inte att det är en vinkel, och kan därför inte interpolera mellan 0deg och 180deg. Den kan interpolera tal, färger, längder – eftersom den känner till deras typer. Custom properties, till skillnad från inbyggda CSS-egenskaper, har ingen typ alls. Det är exakt i denna lucka som CSS Houdini hör hemma.
Vad Houdini egentligen är
Det här är ett vanligt missförstånd: Houdini är inte en enda teknik och inte ens en enda specifikation. Det är ett paraply för flera fristående förslag inom W3C, som förenas av en idé – att exponera delar av renderingsmotorn (värdeparsning, layout, målning, animation) som tidigare var helt dolda för utvecklaren. Istället för att vänta på att en färdig CSS-egenskap som gör exakt det du behöver ska dyka upp, får du en lågnivåkrok in i ett specifikt steg i den processen.
Dessa krokar realiseras som worklets – små JavaScript-moduler, konceptuellt nära Web Workers, men särskilt utformade för renderingspipelinen. En worklet körs utanför huvudtråden, har ingen tillgång till window, document eller DOM, och kommunicerar enbart via ett strikt definierat kontrakt (t.ex. paint(ctx, size, properties)). Det här är ingen begränsning av lathet från specifikationsförfattarnas sida – det är ett medvetet arkitektoniskt beslut. Tack vare isoleringen kan motorn köra worklets parallellt, på separata trådar, och cacha resultatet utan risk att din kod rör vid något den inte borde. Samma isolering är anledningen till att du inte hittar Date, performance.now() eller fetch inuti en worklet – den begränsade åtkomsten till precis tid och nätverk är ett medvetet skydd mot timing-/fingerprinting-attacker från kod som renderar sidan.
De olika delarna av detta paraply befinner sig idag i helt olika skeden: en har länge varit en del av Baseline, en annan fungerar bara i Chrome, och ytterligare en är efter ett decennium fortfarande ett experiment. Den här distinktionen är avgörande – och de flesta introduktionsartiklar om Houdini hoppar över den och behandlar helheten som en enda, färdig teknik att använda.
Den enda delen av Houdini som verkligen är värd att använda idag: @property
CSS Properties and Values API löser exakt det problem som artikeln inleddes med. Du registrerar en custom property med en konkret typ (syntax), ett startvärde och information om huruvida den ska ärvas – och från det ögonblicket behandlar webbläsaren den som ett fullvärdigt, animerbart värde, inte en sträng.
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;}
Det här exemplet gör faktiskt något som vanlig CSS inte klarar av – utan @property skulle ovanstående transition helt enkelt inte fungera, precis som i scenariot i artikelns inledning. syntax: '<angle>' talar om för webbläsaren att --kat är en vinkel, så att den kan interpoleras mjukt; inherits: false förhindrar att den oavsiktligt ärvs av underordnade element, vilket vid custom properties ofta är källan till svårspårade buggar.
Samma effekt kan uppnås från JavaScript, vilket är meningsfullt när du registrerar egenskaper dynamiskt (t.ex. genererade utifrån data) istället för hårdkodat i stilmallen:
javascript
if ('registerProperty' in CSS) {CSS.registerProperty({name: '--kat',syntax: '<angle>',inherits: false,initialValue: '0deg',});}
Det här är den enda delen av Houdini som idag har Baseline-status – den fungerar nativt i Chrome, Firefox (från version 128) och Safari (från 16.4), utan polyfills och utan @supports. Om det är en enda sak ur hela Houdini du ska minnas och implementera, är det just den här.
CSS Paint API: en worklet som faktiskt målar
Paint API låter dig ersätta background med funktionen paint(), som ritar direkt på elementet med hjälp av ett begränsat gränssnitt som liknar Canvas 2D (PaintRenderingContext2D) – utan fillText, drawImage eller pixelavläsning. Det som skiljer det här tillvägagångssättet från vanlig ritning på <canvas> är inputProperties: workleten deklarerar vilka custom properties den vill observera, och ritas om automatiskt enbart när någon av dem ändras – utan en enda rad JavaScript som ansvarar för att lyssna på händelser.
Nedan visas ett mönster med varnande, diagonala ränder vars vinkel styrs av samma, tidigare registrerade egenskap --kat (närmare bestämt dess lokala variant --stripe-angle) – vilket visar hur @property och Paint API naturligt kompletterar varandra.
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.jsif ('paintWorklet' in CSS) {CSS.paintWorklet.addModule('hazard-stripes.js');}
javascript
// hazard-stripes.jsclass 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);
Det är värt att lägga märke till properties.get('--stripe-angle').to('deg').value – det är CSS Typed OM, ytterligare en del av samma API-familj. Istället för att parsa strängen manuellt (parseFloat, klippa bort "deg") får du ett typat värde, CSSUnitValue, som du uttryckligen konverterar till den enhet du behöver. Tack vare att vinkeln på de ritade ränderna enbart beror på den custom property som deklarerats i inputProperties, vet motorn exakt när elementet behöver ritas om – en ändring av en obesläktad CSS-egenskap någon annanstans på sidan triggar inte om paint().
Layout API och Animation Worklet: verkligheten 2026
Här börjar den del som de flesta introduktionsartiklar om Houdini tiger om. CSS Layout API – den tredje pelaren i samma specifikation, som låter dig definiera en egen layoutalgoritm (motsvarigheten till en egen display: grid) – har funnits som förslag sedan 2015 och är efter ett decennium fortfarande experimentellt. Ingen stabil webbläsare har implementerat det nativt; det är enbart tillgängligt bakom flaggor eller i Chromes test-origins. Om du stöter på en artikel som visar display: layout(namn) som en färdig lösning att använda, kontrollera publiceringsdatumet – det är sannolikt en demo från en origin trial för flera år sedan, inte något du kan köra i en användares webbläsare idag.
Animation Worklet var tänkt att ge full kontroll över scrolldrivna animationer som körs utanför huvudtråden. I praktiken stöds den bara av Chromium. Ännu viktigare – dess huvudsakliga användningsfall är inte längre en anledning att ta till Houdini, eftersom CSS har fått en nativ lösning på samma problem: animation-timeline: scroll() gör det idag möjligt att koppla en animation till scrollning utan en enda rad JavaScript och utan worklets, med växande webbläsarstöd. Det här är en bra illustration av ett bredare mönster: en del av skälen till att Houdini över huvud taget skapades har med tiden hamnat direkt i CSS-specifikationen, istället för att förbli ett lågnivå-API för utvecklare.
När det är meningsfullt i praktiken
- Använd
@propertyutan att tveka, utan skyddsåtgärder – den har Baseline-status och behöver varken@supportseller polyfill. - Behandla Paint API som progressiv förbättring – linda in den i
@supports(background: paint(x))med en vettig fallback i ren CSS (t.ex. en vanlig gradient) för Firefox, eller använd polyfillencss-paint-polyfillom effekten måste fungera överallt. - Planera inte en produktionsfunktion kring Layout API – det är fortfarande ett experiment utan reellt webbläsarstöd, bra för att leka med, inte för roadmapen.
- Kom ihåg att
paint()inte är ”gratis” CSS – det är din egen JavaScript-kod, som körs igen varje gång en deklareradinputPropertiesändras. Kostsamma beräkningar (brusgenerering, komplexa procedurella mönster) måste optimeras precis som en renderingsloop på<canvas>– det är lätt att av misstag bygga en animation som ritar om hela elementet bildruta för bildruta på layoutens huvudtråd.
Sammanfattning
Houdini är inte ett enda beslut om ”använda eller inte” – det är tre, fyra separata beslut med olika risknivå. För det första löser @property ett verkligt, konkret problem (att animera custom properties) och är idag lika säkert att använda som vilken annan CSS-egenskap som helst – det är den enda delen av ekosystemet som är värd att implementera utan att tveka. För det andra ger Paint API verklig kraft (en worklet som bara ritas om när de deklarerade beroendena ändras, utan manuell händelselyssning), men kräver en medveten plan för webbläsare utan stöd – det är ett verktyg för progressiv förbättring, inte för kritiska funktioner. För det tredje är Layout API och Animation Worklet ett bra exempel på att en specifikation i sig inte garanterar adoption – efter flera år väntar den ena fortfarande på implementation, medan den andra delvis har ersatts av enklare, nativ CSS. Innan du tar till Houdini, kontrollera i vilken av dessa tre korgar exakt den del du behöver hör hemma.