Say Hello to Houdini and the CSS Paint API
I haven’t been this excited about a new browser tech in a long time.
Houdini is an ambitious project to give developers more power over CSS than we’ve ever had before. The first piece of this project to emerge is the CSS Paint API. This article will explain why Houdini is so exciting, then show you how to get started with the Paint API.
The Classic Disappointment
How many times have you heard about a killer new CSS feature and thought:
“Wow, that’s awesome! Can’t wait to use it… in 2 years, when browsers actually support it.”
If you need more convincing, check out the dark side of polyfilling CSS.
A New Hope
So that’s mildly depressing, but what if I told you that one day, you’ll hear about a new CSS feature and think:
“Wow, that’s awesome! Can’t wait to use it… right now!”
This is what Houdini is striving to deliver. Houdini, in the spirit of the Extensible Web Manifesto, gives developers direct access to the browser’s CSS engine. This gives developers the power to create their own custom CSS features that run efficiently within the browser’s native rendering pipeline.
This means that new CSS features can be implemented via Houdini long before browser vendors implement them. Or you can scratch your own itches by making CSS features that you want, but browser vendors would never implement.
The good news is that Houdini is backed by Apple, Google, Microsoft, Mozilla, and Opera. The bad news is that only Google’s Chrome has implemented anything so far. Here’s the browser support chart at the time of writing.
That chart throws a lot at you, so let me explain.
Houdini is a collection of APIs that are different pieces of the puzzle, so to speak. The Layout API lets you control how elements are laid out with CSS, the Parser API lets you augment how CSS expressions are parsed, and so forth. As you can see, Houdini is very much a work in progress.
But there’s one Houdini API that you can start playing with today: the CSS Paint API. This API lets you draw images for use with CSS properties that expect them — for example,
For now, you’ll need Chrome to play with the Paint API. It’s enabled by default in the latest version of Chrome. If you’re using a version earlier than Chrome 65 (Android phone perhaps?) then the Paint API is still behind a flag — go to chrome://flags and enable Experimental Web Platform features.
And to check via CSS, use this.
The demo below uses both methods to check if your browser supports the Paint API. If you see double checkmarks, then you’re good to go!
An important caveat is that the Paint API only works over
https or on
localhost. If you’re developing locally, http-server makes it super easy to view your pages on
Worklets (which we’ll talk more about in a minute) are cached by your browser, so be sure to disable the cache to see code updates take effect.
Also know that you can’t set breakpoints or use the
debugger statement with code inside of a worklet (yet). Thankfully, you can still use
A Simple Paint Worklet
Let’s make something with the Paint API! We’ll start with something simple that draws an X across an element. We can use this to make placeholder boxes, commonly seen in mockups/wireframes to represent where images will go. Here’s the demo.
setInterval) are not available. This helps keep them efficient and potentially multi-threadable (not done yet, but it’s on the wishlist).
paint() function is called whenever the element needs to be (re)painted. It gives us a couple incoming parameters to work with.
ctx is the object we draw on, just like a
CanvasRenderingContext2D object (documentation here) but with some limitations (like no text drawing).
size gives us the width and height of the element being painted.
Next, we tell the page about our paint worklet. Let’s also add a
<div> with a class of
placeholder while we’re here.
Finally, we connect the paint worklet to the
<div> with some modest CSS.
And that’s it. Congratulations, you’re using the Paint API!
Using Input Properties
As it stands now, our little paint worklet hardcodes the thickness and color of the X. Wouldn’t it be nice if it automatically used the thickness and color of the element’s border?
We can accomplish this with input properties, provided by the Typed Object Model (or Typed OM). It’s another part of Houdini, but unlike the Paint API, it’s still behind a flag. Enable it by going to chrome://flags and enabling Experimental Web Platform features.
You can check for Typed OM support in code with this.
Now let’s update the code for our paint worklet.
inputProperties to tell the paint worklet which CSS properties to look for. After that, the
paint() function can use a third incoming parameter,
props, to access the values for these properties. And now our placeholder boxes are a little smarter.
See the Pen Hello Houdini: Placeholder Box with Input Properties by Will Boyd (@lonekorean) on CodePen.
It’s fine to use
border in our CSS, but keep in mind that it’s actually shorthand for 12 different CSS properties.
The paint worklet needs us to be specific, so for the purposes of this demo, we use
Something cool to note is that
border-top-width is converted to pixels as it’s passed into the paint worklet. This is perfect, since that’s the unit of measurement that
ctx.lineWidth expects. As proof, the third placeholder box in the demo above has a
1rem, but the paint worklet is given
Crafting a Jagged Edge
For our next trick, we’ll make a paint worklet that draws a jagged edge. Here’s the demo.
And here’s the code for the paint worklet.
inputProperties again, this time to control the width and height of each “tooth”. But notice that
--tooth-height are custom properties (also known as CSS variables). This often makes more sense than hijacking an existing CSS property, but it does require another step.
You see, the browser knows that certain built-in CSS properties are length values (like with
border-top-width from earlier). But a custom property can be used for all sorts of stuff. Your browser can’t assume a custom property is being used for length, so we have to tell it.
The Properties and Values API lets us do just that. It’s yet another piece of Houdini and also requires the Experimental Web Platform features flag be enabled at chrome://flags.
You can check for Properties and Values API support in code with this.
Now we can use all sorts of length values with
--tooth-height and your browser will understand them and convert them to pixel values for our paint worklet. We can even use
calc() expressions. Crazy! And if we forget to set them or give them invalid length values, they’ll just fall back to
<length> isn’t the only syntax allowed, as you can see here. So we could also register a
--tooth-color property of syntax
<color>, but I have something better in mind. By using
-webkit-mask-image with our paint worklet, we can “knockout” the jagged edge shape with whatever background we want. The CSS looks like this.
The paint worklet code is exactly the same. Now check out our new fancy jagged edges.
You can also pass values into your paint worklet by using input arguments. These allow you to specify arguments in CSS like so.
The paint worklet declares what it expects via
inputArguments. Then the
paint() function can grab those arguments from a fourth incoming parameter, an array named
args as seen below.
I’m honestly not a fan of input arguments. Custom properties are a lot more versatile. They also help create better self-documenting CSS, since you can use descriptive property names.
New Ways to Animate
Let’s do one last project. Using familiar concepts we’ve already covered, we can create this nifty fading polka dot pattern.
We start by registering some custom properties to control the polka dots.
Then our paint worklet can use these custom properties, along with a bunch of math, to draw the polka dot pattern.
Lastly, here’s the CSS that sets the custom properties and references the paint worklet.
Now for a twist. We can animate the values of registered custom properties in CSS. As the values change, the paint worklet using them will be called upon to repaint with the updated values.
Hover/tap the demo below to see the animation in action.
The potential here is really exciting! We can use paint worklets with custom properties to create completely new types of animation.
Pros and Cons
Let’s review some of the good things about Houdini (and the CSS Paint API in particular).
- Gives you the freedom to create your own visual effects.
- Doesn’t rely on adding extra elements or pseudo-elements to the DOM.
- Executes as part of your browsers rendering pipeline for greater efficiency.
- More efficient and less buggy than polyfills.
- Provides an alternative to using complex/hacky CSS to achieve certain results.
- Serves as a method of abstraction and modularization by containing visual logic within a paint worklet.
- Lets you create completely new types of animation.
- Solves future browser support issues by allowing developers to use new features before browsers implement them.
- All five major browser vendors intend to support Houdini.
Of course, there are shortcomings as well.
- Large pieces of Houdini are still in development.
- Houdini itself requires good browser support before it can start alleviating future browser support issues.
- Browsers have to load a paint worklet file before being able to use it, which can cause style pop-in.
- Current dev tools do not support setting breakpoints or using the
debuggerstatement in a paint worklet (although you can still use
Houdini has the potential to radically change how we approach CSS. It’s still a work in progress, but even just the few working parts we have so far are incredibly interesting. Keep an eye on Houdini.
Thanks for reading!