DEV Community

Tom Quinonero
Tom Quinonero

Posted on • Updated on • Originally published at tomquinonero.com

The Ultimate Guide to CSS Houdini

A complete guide of the Houdini APIs, example of usage and its browser support

What is Houdini anyway? 😠
Me, like two year ago

I learnt a lot about Houdini last year, and I'm amazed by all the possibilities it offers. I'm convinced Houdini is the future of CSS, and it was time for me to write the ultimate guide for it!

Houdini is a collection of browser APIs that allows JavaScript to interact with the CSS rendering engine.

Quite exciting!It allows us to create complex layouts, custom and programmable backgrounds, advanced animations and way more.

In this post, we'll walk through each API of the specification and examine the compatibility.

There are two groups of APIs:

  • Low-level APIs that are the building blocks for the high-level APIs. These are the Type Object Model API, the CSS Properties & Values API, Font Metrics API and Worklets.
  • High-level APIs interact with the CSS rendering engine. They consist of the Paint, the Layout, and Animation API.

Ready? Let's start!!

Low-level APIs

Houdini Low Level APIs: Types Object Model API, CSS Properties & Values API, Font Metrics API and Worklets

Type Object Model API

Interacting with CSS properties using JS can be painful, especially when using units.

You'll have to work with a string containing the whole CSS value, something looking like 120px or 2.4rem.

Type Object Model API exposes these values as a JS object:

{
  value: 2.4, 
  unit: "rem"
}
Enter fullscreen mode Exit fullscreen mode

Much better to work with!

Our DOM elements now got a computedStyleMap method to work with the non-inline style and the attributeStyleMap attribute to work with inline styles.

⚠️ Careful, while attributeStyleMap is an attribute, computedStyleMap is a method (a method is a function in an object) and needs to be called before we can access anything.

Here is how we use these properties:

// Set and get an inline style
element.attributeStyleMap.set("width", CSS.rem(48))
element.attributeStyleMap.get("width")
// => {value: 48, unit: "rem"}

// Set and get computed style (note the "()" after computedStyleMap)
element.computedStyleMap().set("height", CSS.em(12))
element.computedStyleMap().get("height")
// => {value: 12, unit: "em"}
Enter fullscreen mode Exit fullscreen mode

In October 2021 this is supported in every browser except Firefox and Safari.

CSS Properties and Values API

The CSS Properties and Values API allows us to define CSS Custom Properties (aka CSS variables) in a more precise way.

We can now define a type, an initial value and its inheritance behavior.

To define a property, we use registerProperty as such:

CSS.registerProperty({ 
  name: "--brandingColor",
  syntax: "<color>", 
  inherits: false,
  initialValue: "goldenrod",
});
Enter fullscreen mode Exit fullscreen mode

We'll be able to define it in the CSS in the future:

@property --brandingColor{ 
  syntax: "<color>"; 
  inherits: false; 
  initial-value: goldenrod;
}
Enter fullscreen mode Exit fullscreen mode

The syntax property represents the type of the value. It accepts: <number>, <percentage>, <length-percentage>, <color>, <image>, <url>, <integer> and <angle>. There is more on the W3C specification.

Setting the syntax helps the browser knowing how to transition between values.

In CSS, you can transition between colors but cannot between gradients.

Here, by defining --brandingColor we can, for example, animate a gradient 😎

That is how we proceed:

.element{
    --brandingColor: goldenrod;
    background: linear-gradient(90deg, khaki 0%, var(--brandingColor) 100%);
  transition: --brandingColor 800ms ease-in-out;
}

.element:hover{
    --brandingColor: gold;
}
Enter fullscreen mode Exit fullscreen mode

The hover animation will only work if the --brandingColor property type is <color>.

If your browser supports this API, the block should animate on this demo:

In October 2021 this is supported in every browser except Firefox and Safari.

Font Metrics API

Font Metrics API aims to give developers dimensions of text elements. It is really complex and hacky to do this right now, so this will solve a lot.

Unfortunately, this interface is still in its early stage and is not supported in any browser yet.

Worklets

Worklets are scripts that plugs to low-level parts of the rendering engine. It runs JavaScript and WebAssembly code.

Houdini introduces three Worklets: the Pain Worklet, the Layout Worklet and the Animation Worklet that are used to power our high-level APIs.

High-level APIs

Paint API

Houdini CSS: The Paint API

Paint API let us use the 2D Rendering Context to draw backgrounds, text, and borders. We can draw using JS function, and we can use CSS Variables as parameters for this function.

To use the Paint API:

  1. Register the Paint Worklet
  2. Add it as a module
  3. Call it with paint() in your CSS

The Paint Worklet code needs its own JS file.
This is how you register it:

// cornerbox.js

class CornerBox{
  paint(ctx, geom, properties) {
      // The actual painting happens there
  }
}

// Register our class under a specific name
registerPaint('cornerbox', CornerBox);
Enter fullscreen mode Exit fullscreen mode

Then, we need to add it as a module where we put our JavaScript. We also declare the property we might want to animate using the CSS Property and Value API:

//main.js

// We register the property we want to animate
CSS.registerProperty({ 
  name: "--cornerbox-length",
  syntax: "<length>", 
  inherits: false,
  initialValue: "120px",
});

CSS.registerProperty({ 
  name: "--cornerbox-width",
  syntax: "<length>", 
  inherits: false,
  initialValue: "16px",
});

// Add the module from a local file
CSS.paintWorklet.addModule("./cornerbox.js");
// Or add it from a CDN
CSS.paintWorklet.addModule("https://unpkg.com/cornerbox@0.0.3/CornerBox.js");
Enter fullscreen mode Exit fullscreen mode

We can now use paint(cornerbox) in our CSS:

.element {
  width: 20rem;
  height: 20rem;
  --cornerbox-color: #5f64e2;
  --cornerbox-length: 120px;
  --cornerbox-width: 16px;
  background: paint(cornerbox);
  transition: --cornerbox-length 400ms ease-in-out, 
        --cornerbox-width 400ms ease-in-out;
}

.element:hover{
  --cornerbox-length: 220px;
  --cornerbox-width: 24px;
}
Enter fullscreen mode Exit fullscreen mode

We use --cornerbox-length, --corner-width and --corner-color to configure our corner-box.

Have a look at the demo below to see it in action πŸ€™

This wraps up the Paint API section.

Paint Worklets is what hooked me to Houdini in the first place!

There are endless applications to this πŸ€—βœ¨

You should definitely check out Houdini.how! It's a collection of Paint Worklets that are ready to use. This is where the Corner Box worklet comes from.

In October 2021 this is supported in every browser except Firefox and Safari.

Layout API

Houdini CSS: The Layout API

The Layout API allows us to define new layout modes that can be used as a display property in our CSS.

It opens up a lot of possibilities! But this is a complex one and the specification is not definitive yet.

For more information, have a look at the specification on W3.org.

We'll show a working example, but not dive too deep into the code.

In this section, we'll use the Google Chrome Lab's Masonry Worklet.

That is what a Layout Worklet look like (the logic has been removed here):

// masonry.js

registerLayout('masonry', class {
  static get inputProperties() {
    return [ '--padding', '--columns' ];
  }

    static get inputProperties() {
    return [ '--order' ];
  }

  async intrinsicSizes() {}
  async layout(children, edges, constraints, styleMap, breakToken) {
    // The actual code happens there
  }
});
Enter fullscreen mode Exit fullscreen mode

Like a Paint Worklet, let's add it as a module:

// main.js 

// local
CSS.layoutWorklet.addModule("./masonry.js");
// elsewhere
CSS.layoutWorklet.addModule("https://raw.githubusercontent.com/GoogleChromeLabs/houdini-samples/master/layout-worklet/masonry/masonry.js");
Enter fullscreen mode Exit fullscreen mode

And use it in our CSS:

.element{
    --padding: 20;
  --columns: 2;
    display: layout(masonry);
}
Enter fullscreen mode Exit fullscreen mode

And… We got a masonry layout working!

Have a look at the demo:

This is exciting, but not quite ready for now. It is not documented on MDN yet, and the implementation will likely change in the future.

Let's wait a few years on that one!

In October 2021 this feature is hidden behind a flag (Experimental Web Platform features) in every browser except Firefox and Safari.

Animation API

Houdini CSS: The Animation API

Animation API allows us to make advanced animations!

It aims to provide developers with a more performant way of animating using CSS.

Let's register our Animation Worklet:

//superBounce.js

registerAnimator("superBounce", class {
  constructor(options) {
    // Our code goes here
  }
  animate(currentTime, effect) {
    // Our code goes here
  }
});
Enter fullscreen mode Exit fullscreen mode

And add it as a module:

// main.js

CSS.animationWorklet.addModule("./superBounce.js");
Enter fullscreen mode Exit fullscreen mode

To use an Animation Worklet we need to declare what we would normally declare in a @keyframes in JavaScript.

Let's put side to side what we would do with keyframes and what we would do using JavaScript:

// Using the CSS approach

.element{
    animation: bounce 800ms ease-in-out infinite;
}

@keyframes bounce {
  0% {
    transform: scale(1);
  }
  25% {
    transform: scale(1.1);
  }
  50% {
    transform: scale(1);
  }
  75% {
    transform: scale(1.15);
  }
}
Enter fullscreen mode Exit fullscreen mode
// The JavaScript approach

const keyframes = [{
    transform: 'scale(1)',
    offset: 0
  },
  {
    transform: 'scale(1.1)',
    offset: 0.25
  },
  {
    transform: 'scale(1)',
    offset: 0.50
  },
  {
    transform: 'scale(1.15)',
    offset: 0.75
  },
]

const timing = {
  duration: 800,
  easing: "ease-in-out",
  iterations: Infinity
}

const element = document.querySelector('.element--js')
element.animate(keyframes, timing)
Enter fullscreen mode Exit fullscreen mode

Using JavaScript, we can do a bit more than we CSS. For example, we can define the easing in each keyframe.

Also, we can bind the animation progress to Scroll Events, play and pause it at will, change the playback rate, reverse the animation etc…

Here is a demo on CodePen:

That's it, we learned how to make custom animation using the Web Animation API 😎

For a more in-depth read, read the MDN guide about Web Animation API.

In October 2021 this feature is hidden behind a flag (Experimental Web Platform features) in every browser except Firefox and Safari.

Conclusion

I am personally very much excited about all these new features, especially the Paint API!!

I would love to see in the future a lot of available layout and paint worklets that we could customize using CSS variables.
That'll be an outstanding new step for CSS 😍

I'm Tom Quinonero, I write about design systems and CSS, Follow me on Twitter for more tips and resources πŸ€™

Sources and links

Top comments (0)