Aysha Anggraini

On Code, Design, and Movement

CSS Houdini: Properties, Values, and the Paint API

I was invited by Huijing for a talk at a CSS meetup here in Singapore. Small speaking assignments are the best way to explore something new. So I thought I would explore and give a talk on CSS Houdini.

You’ve probably heard a lot about the future of Javascript and in this subject, CSS doesn’t really get much love. But exploring CSS Houdini actually gives you more than a glimpse into the future of CSS. One of the most talked about feature of Houdini is its ability to ‘polyfill’ CSS correctly. But after exploring the APIs, I believe Houdini’s potential extends further than that.

I talked about Houdini’s potential by examining 3 of its APIs and also by comparing how several things are done right now versus how it can be done with Houdini. You can watch the talk on Youtube but I understand that not everyone enjoys watching tech talks (me being one of them. I prefer watching pole dancing tutorials!) and so this article will serve as a write up to what I talked about. It is not really a complete write-up since I will just write about the final demo presented in the talk.

My talk on CSS Houdini at Talk CSS in Singapore. I spoke about the Properties, Typed OM, and Paint API. It is an amazing experience since it is my first time doing a talk with live coding and demos!

The Properties & Values API

CSS variable is amazing is because it is dynamic. But one of its weakness is that it cannot be transitioned. If you try to animate a variable, it will just flip from one property to another. No transitions in between.

This is because CSS variables do not have any meanings. It doesn’t have any type so the browser doesn’t know whether a particular variable is a color, percentage, number, etc.

CSS Houdini gives the ability to assign types to your variables. If you assigned the wrong type of value to the variable, the browser is going to ignore it and pick up the default value instead.

CSS.registerProperty({
 name: '--start',
 syntax: '',
 inherits: true,
 initialValue: 'purple'
})

CSS Houdini’s Properties and Values API allows us to assign type to CSS variables.

With strongly typed CSS variables, it can finally be transitioned. One of the most popular use case for this is animating gradients.

The Paint API

This API gives us the ability to create image programmatically through Canvas for any CSS properties that expect an image. Examples of such properties are background-image and border-image. Instead of passing an image in the url() value, you will need to pass a worklet through the paint() value.

Writing and using a paint worklet

I have always thought that worklet is conceptually the same with web workers. One of the main difference is that worklets are smaller than web workers since it is meant to be called multiple times by the browser. Because of this, it doesn’t have access to any of the global scope items which means there are no setTimeout, setInterval, and requestAnimationFrame.

The Paint API allows you to generate image programatically using Canvas. You would then register the worklet and call it through CSS.

registerPaint('feature', class {
  paint(ctx, geometry, properties) {
    //...
  }
})
.bg {
  background-image: paint(feature);
}

Styling checkboxes with CSS Houdini

Styling checkboxes is one of the most practical examples that I can think of. It is not particularly hard and it gives us the chance to combine Properties & Values API with the Paint API to see how these concepts come together.

Let’s first start by turning the default checkbox into a cross (X). Doing this through canvas is pretty simple.

We can make this look better by combining CSS variables and the Paint API. In this pen, I add several variables in order to give it color and to make the cross further from the edge. We will start by adding these variables through CSS.registerProperty and calling it in our worklet. If you have used CSS variables before, the concept isn’t new. You just need to know how to use it in the worklet.

 CSS.registerProperty({
     name: `--checkbox-color`,
     syntax: '',
     inherits: false,
     initialValue: 'transparent'
   })
      
  CSS.registerProperty({
    name: `--edge`,
    syntax: '',
    inherits: false,
    initialValue: 0
  })
  
  CSS.registerProperty({
    name: `--thickness`,
    syntax: '',
    inherits: false,
    initialValue: 0
  })

Step 1: Register the variables using CSS.registerProperty

 class Checkbox {
      static get inputProperties() {
          return ['--checkbox-color', '--edge', '--thickness'];
      }
      
       paint(ctx, size, props) {
           const color = props.get('--checkbox-color');
           ctx.strokeStyle = color.toString()
       }
}

registerPaint('checkbox', Checkbox)

Step 2: Call the variables in your worklet.

.checkbox {
  --edge: 10;
  --checkbox-color: #ccc;
  --thickness: 5;
   background-image: paint(checkbox);
}

Step 3: Configure the variables in your CSS.

And here is the finish product! Click on the checkboxes to see some magic!

Animation with the Paint API and CSS Variables

Now I mentioned at the top that worklets have to be fast. It will be called multiple times by the browser and it shouldn’t have any latency. This is why it doesn’t have access to any of the global scope items like requestAnimationFrame. Therefore, any animations should be done through CSS variables and CSS transition. Since Houdini gives us the power to animate CSS variables, we can do animation purely through CSS.

In order to make the checkbox look more interesting, I want to animate the cross so that each lines will be animated one after another. Some basic math is required to make this work.

Start by registering 2 variables with type percentage (I call it p1 and p2) to represent the progress of the animation. Then, we can use this variables to draw the lines of the cross. So instead of hard-coding the final value of the lines, we are going to use the variables in order to gradually move the line to its final position. Then, we can animate these variables with CSS keyframes.

// first line
ctx.lineTo((p1 / 100) * finalX, (p1 / 100) * finalY)
// second line
ctx.lineTo(finalX - ((p2 / 100) * finalX) + edge, finalY * (p2 / 100)); 

This is a snippet of code to show how we can use the variables in order to gradually move the lines with animations. For full version, check the codepen below.

/* Since we want to animate the lines one after the other, 
we will use keyframes to describe the state of each lines
during the progress of the animation */
@keyframes draw {
  0% {
    --p1: 0%;
    --p2: 0%;
  }
  
  50% {
    --p1: 100%;
    --p2: 0%;
  }
  
  80% {
    --p1: 100%;
  }
  
  100% {
    --p1: 100%;
    --p2: 100%;
  }
}

/* Configure variables and animation here */
.checkbox:checked {
  --checkbox-color: white;
  --p1: 100%;
  --p2: 100%;
  background-color: deeppink;
  animation: draw 500ms ease-in-out;
}

Define the animation through keyframes. Since we want to animate the lines one after the other, keyframes are used in order to described the state of the lines during the entire progress of the animation. We can then used the animation when the checkbox is checked.

And here is the finish product! Click on the checkboxes to see some magic! Feel free to fork and play around to make the animation better.

What’s next?

I have yet to explore CSS Houdini’s other APIs like the Layout API and the Animation API. I’ll be exploring those and perhaps create some demos out of it. Watch out for future articles/meet-up talks on it. In the meantime, feel free to explore CSS Houdini on your own and the demos shown above to customize or understand the concepts further!