CSS Houdini gebruiken: Maak aangepaste effecten die niet beschikbaar zijn in reguliere CSS
CSS Houdini gebruiken: Maak aangepaste effecten die niet beschikbaar zijn in reguliere CSS
Probeer eens iets ogenschijnlijk eenvoudigs: een hoek in conic-gradient() soepel animeren bij hover. Je definieert een custom property --kat: 0deg, voegt transition: --kat 0.4s toe, beweegt met de muis eroverheen – en er gebeurt niets. De gradient verandert schoksgewijs, alsof transition helemaal niet bestaat. Dit is geen fout in jouw code. Voor de browser is --kat gewoon een ondoorzichtige string – hij weet niet dat het een hoek is, dus kan hij niet interpoleren tussen 0deg en 180deg. Wel kan hij interpoleren tussen getallen, kleuren, lengtes – omdat hij hun type kent. Custom properties hebben, anders dan ingebouwde CSS-eigenschappen, helemaal geen type. Precies in dat gat past CSS Houdini.
Wat Houdini werkelijk is
Dit is een veelvoorkomend misverstand: Houdini is geen enkele technologie en zelfs geen enkele specificatie. Het is een parapluterm voor een aantal onafhankelijke voorstellen bij het W3C, verbonden door één idee – delen van de renderengine (waardeparsing, layout, tekenen, animatie) blootleggen die voorheen volledig verborgen waren voor de ontwikkelaar. In plaats van te wachten tot er in CSS een kant-en-klare eigenschap verschijnt die precies doet wat je nodig hebt, krijg je een laagdrempelige haak in een specifieke fase van dat proces.
Die haken worden gerealiseerd als worklets – kleine JavaScript-modules, conceptueel verwant aan Web Workers, maar specifiek ontworpen voor de renderpipeline. Een worklet draait buiten de hoofdthread, heeft geen toegang tot window, document of de DOM, en communiceert uitsluitend via een strikt gedefinieerd contract (bijv. paint(ctx, size, properties)). Dit is geen beperking uit luiheid van de specificatieschrijvers – het is een bewuste architecturale keuze. Dankzij die isolatie kan de engine worklets parallel uitvoeren, op afzonderlijke threads, en het resultaat cachen zonder risico dat jouw code iets aanraakt wat het niet zou moeten. Diezelfde isolatie is de reden waarom je binnen een worklet geen Date, performance.now() of fetch vindt – de beperkte toegang tot precieze tijd en netwerk is een bewuste bescherming tegen timing- en fingerprinting-aanvallen vanuit code die de pagina rendert.
De afzonderlijke onderdelen van deze paraplu bevinden zich vandaag in heel verschillende stadia: het ene maakt al lange tijd deel uit van Baseline, een ander werkt alleen in Chrome, en weer een ander is na een decennium nog steeds experimenteel. Dat onderscheid is cruciaal – en de meeste inleidende artikelen over Houdini slaan het over, door het geheel te behandelen als één kant-en-klare technologie.
De enige Houdini die je vandaag echt zou moeten gebruiken: @property
De CSS Properties and Values API lost precies het probleem op waarmee dit artikel opende. Je registreert een custom property met een concreet type (syntax), een beginwaarde en de informatie of hij overerfd moet worden – en vanaf dat moment behandelt de browser hem als een volwaardige, animeerbare waarde, niet als een string.
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;}
Dit voorbeeld doet daadwerkelijk iets wat gewone CSS niet kan – zonder @property zou de bovenstaande transition gewoon niet werken, precies zoals in het scenario aan het begin van dit artikel. syntax: '<angle>' vertelt de browser dat --kat een hoek is, zodat hij hem soepel kan interpoleren; inherits: false voorkomt dat onderliggende elementen hem per ongeluk overerven, iets wat bij custom properties vaak een bron is van lastig te achterhalen bugs.
Hetzelfde effect kun je vanuit JavaScript bereiken, wat zinvol is wanneer je eigenschappen dynamisch registreert (bijv. gegenereerd op basis van data) in plaats van hardgecodeerd in een stylesheet:
javascript
if ('registerProperty' in CSS) {CSS.registerProperty({name: '--kat',syntax: '<angle>',inherits: false,initialValue: '0deg',});}
Dit is het enige onderdeel van Houdini dat vandaag de status Baseline heeft – het werkt native in Chrome, Firefox (vanaf versie 128) en Safari (vanaf 16.4), zonder polyfills en zonder @supports. Als je van heel Houdini maar één ding zou moeten onthouden en toepassen, is het dit.
CSS Paint API: een worklet die echt tekent
De Paint API stelt je in staat om background te vervangen door een paint()-functie die rechtstreeks op het element tekent, met een beperkte interface die lijkt op Canvas 2D (PaintRenderingContext2D) – zonder fillText, drawImage of het uitlezen van pixels. Wat deze aanpak onderscheidt van gewoon tekenen op een <canvas>, is inputProperties: de worklet geeft aan welke custom properties hij wil observeren, en tekent zichzelf automatisch opnieuw alleen wanneer een daarvan verandert – zonder één regel JavaScript die verantwoordelijk is voor het luisteren naar events.
Hieronder een patroon van waarschuwende, diagonale strepen, waarvan de hoek wordt aangestuurd door dezelfde, eerder geregistreerde eigenschap --kat (preciezer gezegd, de lokale variant ervan --stripe-angle) – wat laat zien hoe @property en de Paint API elkaar van nature aanvullen.
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);
Het is de moeite waard om aandacht te besteden aan properties.get('--stripe-angle').to('deg').value – dit is CSS Typed OM, een ander onderdeel van dezelfde familie API's. In plaats van een string handmatig te parsen (parseFloat, het bijsnijden van "deg"), krijg je een getypeerde waarde CSSUnitValue, die je expliciet converteert naar de eenheid die je nodig hebt. Omdat de hoek van de getekende strepen uitsluitend afhangt van de in inputProperties gedeclareerde custom property, weet de engine precies wanneer het element opnieuw getekend moet worden – het wijzigen van een niet-gerelateerde CSS-eigenschap elders op de pagina activeert paint() niet opnieuw.
Layout API en Animation Worklet: de realiteit van 2026
Hier begint het deel waarover de meeste inleidende artikelen over Houdini zwijgen. De CSS Layout API – de derde pijler van dezelfde specificatie, waarmee je een eigen layoutalgoritme kunt definiëren (het equivalent van een eigen display: grid) – bestaat al sinds 2015 en is na een decennium nog steeds experimenteel. Geen enkele stabiele browser heeft hem native geïmplementeerd; hij is uitsluitend beschikbaar achter flags of in origin trials van Chrome. Als je een artikel tegenkomt dat display: layout(naam) presenteert als kant-en-klare oplossing, controleer dan de publicatiedatum – het gaat waarschijnlijk om een demo uit een origin trial van jaren geleden, niet om iets wat je vandaag in de browser van een gebruiker draait.
De Animation Worklet moest volledige controle geven over door scroll aangestuurde animaties, uitgevoerd buiten de hoofdthread. In de praktijk wordt hij alleen ondersteund door Chromium. Belangrijker nog – het belangrijkste use case ervoor is niet langer een reden om naar Houdini te grijpen, omdat CSS een native oplossing voor hetzelfde probleem heeft gekregen: animation-timeline: scroll() maakt het vandaag mogelijk om een animatie aan scroll te koppelen zonder één regel JavaScript en zonder worklets, met groeiende browserondersteuning. Dit illustreert goed een breder patroon: een deel van de redenen waarom Houdini ooit is ontstaan, is na verloop van tijd rechtstreeks in de CSS-specificatie terechtgekomen, in plaats van een laagdrempelige API voor ontwikkelaars te blijven.
Wanneer dit in de praktijk zinvol is
- Gebruik
@propertygerust, zonder vangnetten – het heeft de status Baseline en heeft geen@supportsof polyfill nodig. - Behandel de Paint API als progressive enhancement – wikkel hem in
@supports(background: paint(x))met een zinvolle fallback in pure CSS (bijv. een gewone gradient) voor Firefox, of grijp naar decss-paint-polyfill, als het effect overal moet werken. - Plan geen productiefunctie op basis van de Layout API – dit is nog steeds een experiment zonder echte browserondersteuning, leuk om mee te spelen, niet voor de roadmap.
- Onthoud dat
paint()geen "gratis" CSS is – het is jouw JavaScript-code, die opnieuw wordt uitgevoerd bij elke wijziging van de gedeclareerdeinputProperties. Kostbare berekeningen (het genereren van ruis, complexe procedurele patronen) moeten net zo geoptimaliseerd worden als een renderlus op een<canvas>– je bouwt hier gemakkelijk per ongeluk een animatie die het hele element frame voor frame opnieuw tekent op de hoofdthread van de layout.
Conclusie
Houdini is geen enkele beslissing van "wel of niet gebruiken" – het zijn drie, vier afzonderlijke beslissingen met elk een ander risico. Ten eerste lost @property een reëel, concreet probleem op (het animeren van custom properties) en is vandaag even veilig in gebruik als elke andere CSS-eigenschap – dit is het enige onderdeel van dit ecosysteem dat je zonder aarzeling zou moeten toepassen. Ten tweede geeft de Paint API echte kracht (een worklet die zichzelf alleen opnieuw tekent bij wijziging van de gedeclareerde afhankelijkheden, zonder handmatig te luisteren naar events), maar vereist het een bewust plan voor browsers zonder ondersteuning – dit is een instrument voor progressive enhancement, niet voor kritieke functionaliteit. Ten derde zijn de Layout API en de Animation Worklet een goed voorbeeld van het feit dat een specificatie op zich geen adoptie garandeert – na jaren wacht de ene nog altijd op implementatie, terwijl de andere deels is vervangen door eenvoudigere, native CSS. Voordat je naar Houdini grijpt, controleer in welk van deze drie mandjes precies dat onderdeel valt dat je nodig hebt.