
> This post was originally published on <a href="https://octuweb.com/" target="_blank">Octuweb</a> in October 2018.

For many people CSS is a magician's thing, some might even believe it's witchcraft, but that's because they don't know the necessary spells to have real control over CSS.

CSS Houdini is like the magic book that takes us to the next level in our magic control, a series of new spells to control the different phases in the rendering process.

## .CSS

Let's start by understanding the current scenario.

![Rendering progress](https://res.cloudinary.com/nucliweb/image/upload/c_scale,dpr_auto,f_auto,q_auto,w_896/v1693199633/joanleon.dev/assets/css-houdini/rendering-process.png)

In the diagram we can see the rendering process the browser does, converting HTML into the pixels that end up represented on the device.

Once the process starts we have no control, in any of the phases, over how the browser parses HTML and CSS and converts it into the [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction) and [CSSOM](https://developer.mozilla.org/en-US/docs/Web/API/CSS_Object_Model) object models respectively.

If we want to modify the behavior of any CSS property, whether to fix a bug or to adapt the behavior to our content and design, we have to do it with JavaScript once we have the DOM built, select the element to apply the new CSS properties and generate a new CSSOM.

![Rendering process polyfilled](https://res.cloudinary.com/nucliweb/image/upload/c_scale,dpr_auto,f_auto,q_auto,w_896/v1693199633/joanleon.dev/assets/css-houdini/rendering-process-polyfilled.png)

In this other diagram we can see that with JavaScript we can modify the DOM and/or CSSOM, triggering again the Cascade, Layout, Paint and Composite process. This represents an additional cost to the browser, and although it may seem like a totally acceptable cost on our fancy development machine, we must keep in mind that most content consumption comes from mobile devices, where we should pay special attention to minimizing performance impact.

## .CSS-Houdini

> The objective of the CSS-TAG Houdini Task Force (CSS Houdini) is to jointly develop features that explain the "magic" of Styling and Layout on the web. - [CSS Houdini](https://github.com/w3c/css-houdini-drafts/wiki)

CSS Houdini is a collection of new APIs that gives us (partial) access to the browser's CSS engine.

![CSS Houdini rendering process](https://res.cloudinary.com/nucliweb/image/upload/c_scale,dpr_auto,f_auto,q_auto,w_896/v1693199633/joanleon.dev/assets/css-houdini/css-houdini-rendering-process.png)

Here we can see how things change, proposing an API to access each of the phases of the rendering process _(the gray boxes indicate planned specifications, but nothing is defined yet)_.

Let's see what APIs we have "available" currently.

- **Paint API**: this API offers us a (limited) canvas to draw in `background` and `border` properties.
- **Typed OM API**: handles object-based values for working with CSS values in JavaScript.
- **Properties & Values API**: provides an API to define CSS properties and give them a type, behavior and default value. Very useful to use together with other Houdini APIs.
- **Animation Worklet**: comes with the proposal to improve user experience by separating scroll and mouse-related interactions from the main thread. So we'll have smoother animations, transitions and interactions.
- **Layout API**: Build your own layouts, reinvent flexbox and grid and implement everything with CSS properties.
- **Parser API**: exposes the CSS parser, this allows us to parse arbitrary CSS-like languages into a slightly typed representation.
- **Font Metrics API**: exposes some font data in a compact form that gives you much more control over text in your application.

This allows us to **extend CSS through JavaScript**.

## .Extending-CSS

The best way to understand the potential is with examples, so let's start seeing how to work with some of the new APIs.

> For the examples we're going to use Google Chrome Canary and the Experimental Web Platform features flag activated (chrome://flags/#enable-experimental-web-platform-features), as it's the browser with the most support for the new APIs, as we'll see later.

### & .Worklets

A worklet is basically what we're going to use to connect with the browser's CSS engine. It's a JavaScript module where we'll define the "magic" we can use in CSS. We have no control over worklet execution, the rendering engine is what makes the calls when necessary.

The most interesting point of a worklet is that **it has no performance impact**, because each worklet runs within its own execution thread. The power of JavaScript with the smoothness of CSS.

![Worklets](https://res.cloudinary.com/nucliweb/image/upload/c_scale,dpr_auto,f_auto,q_auto,w_896/v1693199633/joanleon.dev/assets/css-houdini/worklets.jpg)

To add a worklet module we'll use the `addModule` method.

```html
<script>
  if ("paintWorklet" in CSS) {
    CSS.paintWorklet.addModule("PlaceholderBoxPainter.js");
  }
</script>
```

In this case we check that we have the Paint API available with the conditional `if ('paintWorklet' in CSS)`, this would allow us to present an alternative and not load the module if it's not supported.

This would be the content of the `PlaceholderBoxPainter.js` module

```js
class PlaceholderBoxPainter {
  paint(ctx, size) {
    // Magic 🎩
  }
}

registerPaint("placeholder-box", PlaceholderBoxPainter);
```

Don't worry if you don't understand something, with the Paint API example the pieces will fit together.

### & .Paint-API

This has been the first API to reach the stable version of Chrome _(version 65, March 2018)_.

In the previous point we saw how to load a worklet module, now let's see an example of the Paint API.

The first thing we're going to do is load our worklet, if it's supported by the browser.

```html
<script>
  if ("paintWorklet" in CSS) {
    CSS.paintWorklet.addModule("PlaceholderBoxPainter.js");
  } else {
    document.querySelector("html").classList.add("no-support");
    // Here we add a class to <html> to show a message
    // or the alternative as fallback
  }
</script>

<div class="placeholder"></div>
```

This is the content of our PlaceholderBoxPainter module.

```js
class PlaceholderBoxPainter {
  paint(ctx, size) {
    ctx.lineWidth = 2;
    ctx.strokeColor = "#FC5D5F";

    // Draw a line from top left
    // to bottom right.
    ctx.beginPath();
    ctx.moveTo(0, 0);
    ctx.lineTo(size.width, size.height);
    ctx.stroke();

    // Draw a line from top right
    // to bottom left
    ctx.beginPath();
    ctx.moveTo(size.width, 0);
    ctx.lineTo(0, size.height);
    ctx.stroke();
  }
}

registerPaint("placeholder-box", PlaceholderBoxPainter);
```

The first detail we can see is that we're defining a class, and we won't need a transpiler like Babel, since the browser interprets it natively.

In the example we have a single method, `paint()` which is what will be executed every time the CSS engine needs to repaint the element. We have two initial parameters, `ctx` the context where we can use a limited version of the `CanvasRenderingContext2D` object (we can't draw text or work with bitmaps) and the second parameter is `size` where we have the element's width and height.

As indicated in the code comments, our new worklet will paint an X, as is usually used as a placeholder in mockups to reference an image.

We've defined `ctx.lineWidth = 2` and `ctx.strokeColor = '#FC5D5F'` as default values for thickness and color.

Finally, with `registerPaint('placeholder-box', PlaceholderBoxPainter)` we're registering the module, where we pass as first parameter the name we'll use in CSS and the name of the class we just defined.

Let's see how the CSS looks.

```css
.placeholder {
  background-image: paint(placeholder-box);
  ...;
}
```

This is where we see a new function, `paint(placeholder-box)` with the parameter we used in the module registration.

> Like any CSS property, we can define a fallback by adding a previous line.

```css
.placeholder {
  background-image: linear-gradient(to bottom, #fc5d5f 0%, #a53d3d 100%);
  background-image: paint(placeholder-box);
  ...;
}
```

Here you can see the example [Placeholder Box](https://codepen.io/lonekorean/full/wmwJQX/)

#### &-CustomProperties

Let's make our module more dynamic by adding **Custom Properties** to our class.

##### CSS

```css
.placeholder {
  --line-with: 2;
  --stroke-color: #fc5d5f;
  background-image: linear-gradient(to bottom, #fc5d5f 0%, #a53d3d 100%);
  background-image: paint(placeholder-box);
  ...;
}
```

Here we've added two new CSS properties and assigned them a value. You'll have seen that they're not properties you recognize, don't worry we'll talk about them in a moment.

##### Worklet

```js
class PlaceholderBoxPainter {
  static get inputProperties() {
    return ["--line-with", "--stroke-color"];
  }

  paint(ctx, size, properties) {
    ctx.lineWidth = properties.get("--line-with");
    ctx.strokeColor = properties.get("--stroke-color");

    // Draw a line from top left
    // to bottom right.
    ctx.beginPath();
    ctx.moveTo(0, 0);
    ctx.lineTo(size.width, size.height);
    ctx.stroke();

    // Draw a line from top right
    // to bottom left
    ctx.beginPath();
    ctx.moveTo(size.width, 0);
    ctx.lineTo(0, size.height);
    ctx.stroke();
  }
}

registerPaint("placeholder-box", PlaceholderBoxPainter);
```

As you'll have seen, we've added several lines of code to our worklet module.

On one hand we've added a static method to get the properties we have defined in the CSS declaration context. Here we define the properties we want to use in our worklet, in this case `--line-with` and `--stroke-color`.

```js
static get inputProperties() {
  return [
    '--line-with',
    '--stroke-color'
  ];
}
```

We've also changed the line thickness and color assignment.

```js
ctx.lineWidth = properties.get("--line-with");
ctx.strokeColor = properties.get("--stroke-color");
```

##### JS

```js
CSS.registerProperty({
  name: "--line-with",
  syntax: "<number>",
  inherits: false,
  initialValue: 1,
});

CSS.registerProperty({
  name: "--stroke-color",
  syntax: "<color>",
  inherits: true,
  initialValue: "rebeccapurple",
});
```

Here another of the new APIs comes into play, the **Properties & Values API** _(operational in the Canary version of Google Chrome)_. With which we can register our own CSS properties. I think this API is amazing, let's analyze the object we're passing as argument.

- `name` with which we define the CSS property name.
- `syntax` allows us to define the "type" of accepted data, in case it's not the defined one it ignores it.
- `inherits` with a boolean value we indicate whether it accepts inheritance from parent or not.
- `initialValue` the initial value of the property.

By defining these properties, we can use them as if they were variables to have them available in our worklet module.

You can see the changes [here](https://codepen.io/nucliweb/pen/KGxMWB)

## .Support

I know this part is what discourages many people, but like any advance in web development we have an implementation period in browsers. [Surma](https://twitter.com/DasSurma) maintains the page **[Is Houdini ready yet‽](https://ishoudinireadyyet.com/)**, where he presents a table where we can see API support in different browsers.

![Is Houdini ready yet‽](https://res.cloudinary.com/nucliweb/image/upload/c_scale,dpr_auto,f_auto,q_auto,w_896/v1693199633/joanleon.dev/assets/css-houdini/ishoudinireadyyet.jpg)

Yes, everything is still very green, well very red 😅.

The only API with Candidate Recommendation at the W3C is the Paint API, which is already available in stable versions of Chrome and Opera, as is the Type OM API, even with a Working Draft status at the W3C. Activating experimental flags in Canary, we can start playing with Layout API, Properties & Values API and Animation Worklet.

## .Conclusion

I've read several articles defining CSS Houdini as the solution for developing polyfills for CSS or as the Babel for CSS _(which I think is a [totally wrong approach](https://medium.com/@nucliweb/hi-stoyan-d5da3e91715d))_.

You might be wondering, as [Naiara](https://twitter.com/nabaroa), [Diana](https://twitter.com/diana_aceves_) or [Ángel](https://twitter.com/ancoar) surely do 😉, if we'll be able to define custom behaviors that aren't defined in the W3C specification, isn't that moving away from the philosophy of web standards?

IMHO, I think with this proposal, they're giving us access to new APIs that offer us more "power" and control. And like any new technology it will have an acceptance period where we'll see examples of not very consensual implementation... but for now, **let's enjoy the option of improving our magic with these new spells**.

## .References

I'll leave you a [list of resources](ListaDeRecursosMolones) where you'll find links to documentation, articles, videos and most importantly: examples where you can analyze behavior _(remember to use Chrome Canary with `chrome://flags/#enable-experimental-web-platform-features` activated)_.
