This article is based on my Polish blog post about same topic.

If you’re following JavaScript community, you probably know about CSS in JS. It’s a way to put your CSS into your JavaScript file, instead of separated CSS file. The solution I’m showing in this article is the opposite, it’s JSON object put into your CSS file that is parsed in JavaScript (you can probably put JS in CSS but I didn’t test it). The reason why we do this is to control JavaScript based CSS, the API that is part of Houdini.

Houdini

Houdini is collection of new API created for one purpose. So the developers can be in control of how CSS engine behave. To be able to hook up in its internals and make it work like they want. Some of the API are already implemented, some are planed to be implemented and some are still in design process so they may change. You can see the status of the Houdini on a site ishoudinireadyyet.com.

Paint Worklet

This worklet is like normal worker (separated thread run in the browser) but it allow to register “paint” class that have access to canvas like API that can be used inside CSS to render things (e.g. using background-image)

To register paint worklet you execute this code:

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

Inside that file you can create special class like the one below:

class Circle { static get inputProperties() {

return ['--pointer-x', '--pointer-y', '--pointer-options'];

} paint(context, geom, properties) {

var x = properties.get('--pointer-x').value || 0;

var y = properties.get('--pointer-y').value || 0;

const prop = properties.get('--pointer-options');

// destructure object props with defaults

const {

background,

color,

width

} = Object.assign({

color: 'white',

background: 'black',

width: 10

}, JSON.parse(prop.toString()));

// draw circle at point

context.fillStyle = color;

console.log({x,y, color, background, width})

context.beginPath();

const r = Math.floor(width / 2);

context.arc(x, y, r, 0, 2 * Math.PI, false);

context.closePath();

context.fill();

}

}

then you can register this class as paint:

registerPaint('circle', Circle);

JSON in CSS

If you take a look at the previous code of the class you will see this:

const prop = properties.get('--pointer-options');

JSON.parse(prop.toString());

So we get raw data from CSS and parse it as JSON. In CSS it look like this:

div {

height: 100vh;

background-image: paint(circle);

--pointer-x: 20px;

--pointer-y: 10px;

--pointer-options: {

"color": "rebeccapurple",

"width": 20

};

}

Each time you change custom property (the one with two dashes at front, also called css variable) it will re-render the background.

NOTE: Last time I was tested this code, CSS variables required to have dash in the middle, otherwise they were not working with paint worklet.

When you add custom properties to your CSS, you also need to register them, so they they have type:

CSS.registerProperty({

name: '--pointer-x',

syntax: '<length>',

inherits: false,

initialValue: '10px'

});

CSS.registerProperty({

name: '--pointer-y',

syntax: '<length>',

inherits: false,

initialValue: '10px'

});

CSS.registerProperty({

name: '--pointer-options',

inherits: false,

initialValue: '{}'

});

And last thing you can do is to change the CSS properties on mouse move:

document.querySelector('div').addEventListener('mousemove', (e) => {

const style = e.target.style;

style.setProperty('--pointer-x', event.clientX + 'px');

style.setProperty('--pointer-y', event.clientY + 'px');

});

You can check whole code in this CodePen demo.

If you like this article you can follow me on twitter @jcubic.