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?

  1. Declare a custom paint class
  2. Register paint
  3. 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.

  • 678