As I mentioned in my recent article about canvas animation inside React components, I love HTML canvas. So I couldn’t be more excited to learn about the new CSS Houdini APIs when I was reading a short article about it on css-tricks.com, by Stephen Fulghum.
The main reason for my excitement is that the Painting API makes it possible to create custom CSS images by drawing onto a PaintRenderingContext2D (which is pretty much an exact copy of the 2D context that we draw onto when we use the regular Canvas API — except for a small subset of features).
By using the Painting API we can programmatically draw images and use those images in our CSS. When drawing, we can receive parameters with information coming from the DOM and applied stylesheets.
The MDN Web Docs describe it like this:
Houdini is a set of low-level APIs that exposes parts of the CSS engine, giving developers the power to extend CSS by hooking into the styling and layout process of a browser’s rendering engine.
I find it very exciting, especially since we see signs that all major browsers are implementing this:
Is Houdini Ready Yet?
Version Opera 52. Release date: March 22, 2018 Notes Debugging (i.e. breakpoints) do not work. No support for `paint()`…
Important note: CSS Houdini is still an experimental technology, in general. But as mentioned before, most browsers are implementing it or are strongly considering implementing it. Google Chrome is an early adaptor and ships support for the Painting API since version 65, so that’s the browser we will be using today.
CSS Painting API
By using this API we can programmatically draw images and use those images in our CSS. This is what we will create:
Impressively enough, these are three DIV elements and three DIV elements only.
Our index.html file is evidence for that:
The “styling” (drawing…) for the background of the three panes is done with the Painting API, programmatically. All three panes are drawn by the same function.
Let’s have a look at the CSS class
pane in our stylesheet:
We paint something called pane, on line 2. That requires explanation, and we will have a look at it soon.
It might be important to note that the paint function is not executed only once. It’s executed — and will re-paint the image — whenever the render engine of the browser gives it instructions to do so. Examples of that happening are when the user resizes the browser window, or when other CSS properties of the DIV element change, and the DIV element gets another dimension.
Since our function will be “hooked into” the render engine, this is a very performant and low-cost operation.
Other than that it’s worth mentioning that all our panes have a different font size (keep this in mind, we will refer to it later on again). And we see two custom CSS variables being used:
CSS variables are nothing new. They have been around since 2014/2016 (resp. Firefox/Google Chrome). But one of the new CSS Houdini APIs called CSS properties and values API allows us to register these custom variables so the browser will know more about them, which will come in handy.
Inside our stylesheet we can register them like this:
So why do we do this? Now our browser, and its render engine, know the details about these properties. It knows that
‑‑pane‑color contains a color value and that the default value is “#646464”. And
‑‑dot‑spacing contains a length value, defaulting to “5px.”
Our two new variables are now called registered custom variables.
Back to this line:
The Painting API allows us to paint an image. The
main.js), which is loaded inside index.html, like this:
If that call returns an error, your browser does not support the Painting API.
Now our Paint class “Pane” is registered under the name “pane,” and we can use it in our CSS as we saw earlier:
Details of the Paint class Pane
Let’s go over the details of the Paint class inside worklet.js:
The static function
inputProperties should return a list of CSS properties that we’re interested in when we will draw our image. This is arbitrary, and you can add any CSS property you want.
The value returned by
contextOptions indicates that we want to be able to use transparency in our canvas.
And finally, on line 10, the
paint function. In that function, we will do the actual drawing. In our case, it receives three parameters:
ctx: the 2D context of our canvas. This should ring a bell if you’re familiar with the regular HTML Canvas element.
size: a PaintSize instance with two properties:
.height. Those are the computed dimensions of the HTML element that we’re drawing an image for. Including the padding, if set.
styleMap: a read-only representation of a CSS-declaration block (source). It’s an instance of a
StylePropertyMapReadOnlyand only contains values for the properties we defined inside the static function
Tip: You can get a complete StylePropertyMapReadOnly (containing all the computed CSS styles) for any HTML element in the DOM by calling computedStyleMap, in browsers that support it:
const styleMap =
To clarify how we can retrieve values from such an instance, I created a snippet with some inline comments:
Painting the Actual Image
Back to the logic of our little demo. The only thing left is the content of the body of our paint function.
We could start by simply drawing a filled rectangle as our background-image (you should recognize the commands from drawing on a regular HTML canvas):
…which would look like this:
Notice how we used our custom CSS property
‑‑pane‑color to dynamically set the fill style.
By using the dev-tools of our browser we can even update the color value, and the background-image will be re-painted instantly!
If we update our paint function with some more fancy logic (you can look at the details in the worklet.js file in the repository), our background-image will look like this:
Notice the dot sequence in the bottom-right corner. The spacing between those dots is determined by the value of the custom CSS property
‑‑dot‑spacing, which has a default value of “5px.”
Let’s have a bit of fun by adding a transition to this property. We also increase the value of the dot-spacing when we hover over our
…which results in this smooth animation when hovering our elements:
It illustrates how performant our custom paint function is and that it’s very capable of running 60+ times per second.
The reason we’re able to add a transition to a custom CSS variable is that we registered the property earlier on (see lines 1–5 in the gist).
Tip: We could also add an infinite CSS animation for one of our custom CSS properties which would lead to us having a custom drawn animated background!
I find the Houdini Painting API not only interesting but also easy to understand and powerful (performant). My mind is full of ideas for using this new functionality, but I have to be patient and wait until all major browsers support it before we can consider using this in production.
Thanks for your time!