The CSS Paint API

Avatar of Ruth John
Ruth John on (Updated on )

📣 Freelancers, Developers, and Part-Time Agency Owners: Kickstart Your Own Digital Agency with UACADEMY Launch by UGURUS 📣

The CSS Paint API is extremely exciting, not only for what it is, but what it represents, which is the beginning of a very exciting time for CSS. Let’s go over what it is, why we have it and how to start using it.

What is the CSS Paint API?

The API is just one part of a whole suite of new specifications all under the umbrella of what is known as CSS Houdini. Houdini, in essence, gives developers lower level access to CSS itself. No kidding.

The CSS Paint API specifically allows you to call a paint() function wherever you would normally write a value where an image is expected. A common example is the background-image property, where you might use the url() function to a link to an image file, like this:

area {
  background-image: url('assets/myimage.jpg');
}

The CSS Paint API allows you to call the paint() function instead and pass in a paint worklet that you have defined through JavaScript. Think of as a bit of code that allows you to programmatically draw (pretty much) whatever you like. And because it’s JavaScript, you can make it dynamic, too. The API itself is very much like the HTML5 <canvas> API (and we’ll get to how that works in a minute).

This sounds cool, but complicated. I’m already happy using regular images…

That’s great! There’s absolutely nothing wrong with using regular images like you have been doing. Just because something is new and probably cool doesn’t mean we all have to start using it for everything. However, images are static and the idea of generating something dynamic is enticing!

Let’s think about linear-gradient for a moment. It’s extremely powerful. I mean, check these out. But, can you imagine how much less work it would be creating those layered patterns without multiple background images? Not only this, but digging in to the CSS Paint API should also help you understand how these kind of images are generated during runtime, which could be really helpful (and is something we’re about to do).

What about conic-gradient which has no browser support yet (there is some support now, but the point this is trying to make stands) … without a polyfill, that is. Harnessing the Paint API would allow you to create a conic gradient and some properties to change it much the same as the actual specification. So, in reality, you would actually be creating your own native polyfill. That’s pretty neat!

Remember, this is all part of the bigger CSS Houdini group of features. Here’s a quote from the CSS Houdini Wiki:

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.

Sounds nice, right? It is and these new features are aimed at allowing developers to extend the functionality of CSS itself, providing better control, cross-browser support and polyfilling.

The standards process can take a while: from proposing a new CSS feature, to writing a specification, to having the browser vendors implement this new specification. And since developers are often eager to start using a new feature as soon as possible, we have to consider that legacy browsers may not support it and possibly any changes to the specification if it hasn’t been fully implemented — and that’s not to mention the typical nuances of cross-browser implementation. Houdini could go quite a way to help with that, by allowing us to implement browser functionality ourselves while we wait for vendors to catch up.

Philip Walton does a great job of explaining these benefits of Houdini and more in this great article on Smashing Magazine.

You can see how Ana Tudor has already put it to use to create complex animations.

So, can I use it now?

You can! The CSS Paint API is only supported in Chrome 65 at the time of writing, but here’s a support chart that will stay updated over time:

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

ChromeFirefoxIEEdgeSafari
65NoNo79No

Mobile / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
122No122No

But, there’s still a ways to go.

Regardless, let’s take a look at how we go about creating the code to use it. I’m going to break this down into three stages. There’s some new JavaScript features we’re going to use along the way, but the code is there for you and we’ll go through each.

Step 1: The CSS

First, we need to name our worklet and call it in the CSS. I’m going to call it awesomePattern, so my CSS would look something like this:

section {
  background-image: url('fallback.png');
  background-image: paint(awesomePattern);
};

We’re all set up, but nothing is going to happen just yet.

Step 2: The JavaScript

Now, we need to add our paint worklet to our main JavaScript, which is loading another JavaScript file like so:

CSS.paintWorklet.addModule('patternWorklet.js');

Still, nothing will happen at this point because it’s within our patternWorklet.js file, where all the work is going to be done.

In patternWorklet.js, we need to register a paint worklet class:

registerPaint('awesomePattern', Shape);

So we call the registerPaint() function and pass what we want to call the paint worklet, in this case awesomePattern, and then a reference to the class we are about to write, in this case Shape. Remember to add this after the class we’re about to define next. Unlike function hoisting in JavaScript, you do need to define classes before you can use them.

Next, we are going to use the ECMAScript2015 classes syntax to write a class that will draw our background for us. Because it is now registered as a paint worklet class, we get some things for free.

class Shape {
  paint(ctx, geom, properties) {

    ctx.strokeStyle = 'white';
    ctx.lineWidth = 4;
    ctx.beginPath();
    ctx.arc( 200, 200, 50, 0, 2*Math.PI);
    ctx.stroke();
    ctx.closePath();

  }
}

In the paint callback, we have ctx, geom and properties. ctx is the same as a 2D context we would get from a <canvas> element. (Well, almost: a <canvas> element allows the ability to read back pixel data, whereas you can’t with the CSS Paint API.) It allows us to use all the same methods to draw as we would if we were on canvas. In the example above, I am simply drawing a circle with the arc function.

The first two values of the arc function are the X and Y coordinates of where the circle is positioned, in pixels, from the top-left of the element. But, I would like the circle to be central and this is where the geom data comes in handy. It’s actually passing back the PaintSize, which is the size of the area the image would normally fill and we can access width and height information, which is precisely what I need to make the circle central:

See the Pen Simple Circle: CSS Paint API Example by Rumyra (@Rumyra) on CodePen.

Great, but this is a pretty simple example. Let’s switch out the circle for something a little more awesome:

See the Pen Simple Circle: CSS Paint API Example by Rumyra (@Rumyra) on CodePen.

Here, I’ve added a method to the class called drawStar and there’s a whole bunch of canvas functions going on there, that draw the CSS-Tricks logo.”

Step Three: Custom Properties

We can do more! We can harness the power of CSS custom properties (aka variables). We’ve all been really excited by them and this is one of the reasons why.

Let’s say I want to be able to change the size or color of our CSS-Tricks logo? Well we can put those parameters as custom properties in our CSS and access them with the third piece of callback data, properties.

Let’s add a --star-scale property to our CSS, which will eventually make our logo bigger and smaller, and a --star-color property to easily change the color of the logo directly in the CSS.

section {
  --star-scale: 2;
  --star-color: hsla(200, 50%, 50%, 1);
  background-image: paint(awesomePattern)
};

Back in our paint worklet class, we need to access those custom properties. We do this with the inputProperties method, which gives us access to all CSS properties and their given values:

static get inputProperties() { return ['--star-scale','--star-color']; }

Now, we can access them within the paint method:

const size = parseInt(properties.get('--shape-size').toString());

…and use that value within our code. So, if we change either the --star-scale or --start-color properties within out CSS, it will update the background!

It’s also worth noting that all your usual CSS background properties work as expected, such as background-size and background-repeat. Which is great to know and still really useful!

Conclusion

This is pretty powerful stuff, and not just for custom background images. Imagine a half or a double border on your element. You might normally use the ::before or ::after pseudo-elements, or maybe a cleverly thought-out box-shadow. Well, you could implement that (and more) with the CSS Paint API and the border-image property.

This API really pulls together a lot of cool features, like worklets, ECMAScript2015 classes and canvas. Plus, it gives us the whole interaction layer that comes with JavaScript. You could easily add events to update the custom properties and thus the image itself, like this example from Surma, who harnesses the click event to start updating properties within a requestAnimationFrame function to create an animation every time the user clicks on a button. It even takes the coordinates of the click into account.

This may seem a little convoluted on the face of it, but let’s take a look at some of the other parts of the Houdini suite we’re about to encounter:

  • The CSS Layout API, which should allow us to do something like display: layout('myCustomLayout'), for which the typical example would be a custom masonry type layout, but the scope is a lot more.
  • The CSS Properties and Values API which allows us to specify types for custom properties.
  • The CSS Animation Worklet API, which takes animation processing from the main thread, which should result in silky smooth animations.

So, regardless of whether you’re really interested in this specific new feature or not, it is part of a whole bunch of new technology that will unlock a great deal of power. Watch this space because CSS is about to get even more awesome!