Make the Web Brighter with the
CSS Paint API
Vitalii Bobrov
Β Β Β Β @bobrov1989
https://vitaliy-bobrov.github.io/

- 5 years of front-end
- build enterprise web apps
- CSS experimenter
- mentor
- open-source contributor

Agenda
- What is the Houdini Project?
- CSS Paint API
- Examples
Houdini
π
CSS Paint API
Houdini
π² π§
π€ π½
We want to use the feature after browser implementation....
We will implement the feature when more developers will use it...
π² π§
π€ π½


CSS Engine Internals
Parser
CCSOM
Cascade
Layout
Paint
Composite

JS Polyfills
JavaScript Polyfilling
- Too Big
- Too Slow
- Too Incorrect
Compile Polyfills


CSS Paint API
ποΈ
Used for ANY Property that expects an Image
- background-image
- border-image
- list-style-image
- custom <image> property
Usage
some-element {
background-image: paint(my-custom-paint);
}

CSS Fallback
some-element {
background-image: url('./assets/fallback.jpg');
background-image: paint(my-custom-paint);
}
PostCSS Fallback
some-element {
background-image: paint(my-custom-paint);
}
some-element {
background-image: url('./assets/fallback.jpg');
background-image: paint(my-custom-paint);
}
Processed on build
const postcss = require('postcss');
module.exports = postcss.plugin('postcss-fallback-my-paint', (options) => {
return css => {
css.walkRules(rule => {
rule.walkDecls(decl => {
const value = decl.value;
if (value.includes('my-custom-paint')) {
decl.cloneBefore({value: options.fallbackValue});
}
});
});
};
});
PostCSS Plugin
How to create Custom Paint?
- Declare a custom paint class
- Register paint
- Load worklet
1. Declare a custom paint class
class MyCustomPainter {
paint(ctx, geom, props, args) {
/// paint implementation.
}
}
2. Register paint
registerPaint('my-custom-paint', MyCustomPainter);
3. Load worklet
if ('paintWorklet' in CSS) {
CSS.paintWorklet.addModule('custom-paint.js');
}
Examples
π¨
Don't Try this at Home
β’οΈβ
CSS Paint Hello World
class CirclesPainter {
paint(ctx, geom) {
const offset = 10;
const size = Math.min(geom.width, geom.height);
const radius = (size / 4) - offset;
const point = radius + offset;
...
}
}
registerPaint('circles', CirclesPainter);
https://vitaliy-bobrov.github.io/css-paint-demos/hello-world/
CSS Paint Hello World
paint(ctx, geom) {
...
for (let i = 0; i < 2; i++) {
for (let j = 0; j < 2; j++) {
ctx.fillStyle = `rgb(0,
${Math.floor(255 - 42.5 * i)},
${Math.floor(255 - 42.5 * j)})`;
ctx.beginPath();
ctx.arc(
point + (i * (point * 2)),
point + (j * (point * 2)), radius, 0, 2 * Math.PI);
ctx.fill();
}
}
}
https://vitaliy-bobrov.github.io/css-paint-demos/hello-world/
CSS Paint Hello World
.circles {
background: #000;
background: paint(circles);
}
https://vitaliy-bobrov.github.io/css-paint-demos/hello-world/
CSS Paint + variables
static get inputProperties() {
return [
'--circles-offset',
'--circles-count',
'--circles-opacity'
];
}
paint(ctx, geom, props) {
const offset = parseInt(props.get('--circles-offset'), 10) || 0;
const count = parseInt(props.get('--circles-count'), 10) || 2;
const opacity = parseFloat(props.get('--circles-opacity')) || 1;
...
}
https://vitaliy-bobrov.github.io/css-paint-demos/circles-with-params/
CSS Paint + variables
.circles {
--circles-count: 2;
--circles-offset: 10;
--circles-opacity: 1;
background: #000;
background: paint(circles);
}
https://vitaliy-bobrov.github.io/css-paint-demos/circles-with-params/
Experiment
Think OUT of the BOX
π€―
CSS Paint Chart
paint(ctx, geom, props) {
const outerR = parseInt(props.get('--chart-outer-radius'), 10) || 100;
const innerR = parseInt(props.get('--chart-inner-radius'), 10) || 0;
const value = parseFloat(props.get('--chart-value')) || 0;
const background = props.get('--chart-background') || 'red';
const foreground = props.get('--chart-foreground') || 'blue';
...
}
https://vitaliy-bobrov.github.io/css-paint-demos/circle-chart/
CSS Paint Chart
paint(ctx, geom, props) {
...
ctx.fillStyle = background;
ctx.beginPath();
ctx.arc(x, y, outerRadius, 0, Math.PI * 2, false);
ctx.arc(x, y, innerRadius, 0, Math.PI * 2, true);
ctx.fill();
ctx.fillStyle = foreground;
const sRadian = Math.PI * 1.5;
const eRadian = Math.PI * 2 * (0.75 + value / 100);
ctx.beginPath();
ctx.arc(x, y, outerRadius, sRadian, eRadian, false);
ctx.arc(x, y, innerRadius, eRadian, sRadian, true);
ctx.fill();
}
https://vitaliy-bobrov.github.io/css-paint-demos/circle-chart/
CSS Paint Chart
.chart {
--chart-inner-radius: 0;
--chart-outer-radius: 100;
--chart-value: 10;
--chart-background: #795548;
--chart-foreground: #FF5722;
background: #999 paint(circle-chart);
}
https://vitaliy-bobrov.github.io/css-paint-demos/circle-chart/
https://vitaliy-bobrov.github.io/css-paint-demos/qr-code/
CSS Arguments
.gradient {
background: linear-gradient(to top, white, red);
}

chrome://flags/
Chart + arguments
static get inputArguments() {
return ['<color>', '<color>'];
}
paint(ctx, geom, props, args) {
...
const [background, foreground] = args;
ctx.fillStyle = background.toString();
...
ctx.fillStyle = foreground.toString();
...
}
https://vitaliy-bobrov.github.io/css-paint-demos/circle-chart-args/
Chart + arguments
.chart {
--chart-inner-radius: 0;
--chart-outer-radius: 100;
--chart-value: 10;
background: paint(circle-chart, #795548, #FF5722);
}
https://vitaliy-bobrov.github.io/css-paint-demos/circle-chart-args/
Animations
https://lab.iamvdo.me/houdini/ripple
requestAnimationFrame(function raf(now) {
const count = Math.floor(now - start);
button.style.cssText = `
--ripple-x: ${x};
--ripple-y: ${y};
--animation-tick: ${count};
`;
if (count > 1000) {
button.classList.remove('animating');
button.style.cssText = `--animation-tick: 0`;
return;
}
requestAnimationFrame(raf);
});
RAF? What?!
π€
.my-elem {
--my-color: #fff;
background: var(--my-color);
}
String interpolation
.my-elem {
--my-color: #fff;
background: --my-color;
transition: --my-color .3s;
}
.my-elem:hover {
--my-color: red;
}
Custom Properties
CSS.registerProperty({
name: '--my-color',
syntax: '<color>',
inherits: false,
initialValue: 'white'
});
https://lab.iamvdo.me/houdini/animating-gradient
Resources
- https://developers.google.com/web/updates/2018/01/paintapi
- https://drafts.css-houdini.org/css-paint-api/β
- https://www.w3.org/TR/css-paint-api-1/β
- https://github.com/w3c/css-houdini-draftsβ
- https://lab.iamvdo.me/houdini/
- https://vitaliy-bobrov.github.io/css-paint-demos/β
Thank YOU!
Β Β Β Β @bobrov1989
https://vitaliy-bobrov.github.io/

Make Web Brighter with CSS Paint API
By Vitaliy Bobrov
Make Web Brighter with CSS Paint API
CSS Paint API allows you to programmatically generate an image for any CSS property that can use images. With few lines of JavaScript, you could create and register own CSS functions like linear-gradient. Why is this feature so cool? Where to use it? Is it ready for production? This talk will answer those questions through practical examples.
- 634