CSS houdini初探

CSS houdini是一个各大厂商的工程师组成工作组,志在建立一系列的API,让开发者能够介入浏览器的CSS 引擎操作。

浏览器支持详情见https://ishoudinireadyyet.com/,简单来说,还是Chrome支持最好,其中Paint API(Chrome 65)和Type OM(Chrome 66)已经完全支持。

今天就简单介绍一下Properties & Values API和Paint API

Properties & Values API

Properties & Values API扩展了CSS variables,可以注册有值类型,初始值和已定义的继承行为的属性。使用registerProperty(PropertyDescriptor)方法来注册自定义属性,其中PropertyDescriptor是一个配置选项对象,包含四个属性。

  • name,自定义属性名称
  • syntax,如何解析该自定义属性
  • inherits,是否继承DOM tree,bool类型
  • initialValue,初始值

下面看一个谷歌开发者文档的一个demo,贴出部分代码如下。(如果Chrome没有效果,访问chrome://flags开启Experimental Web Platform features

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.box {
width: 30vmin;
height: 30vmin;
animation: 3s change infinite alternate linear;
background-color: var(--animated-color)
}
@keyframes change {
from {
--animated-color: var(--color-a);
}
to {
--animated-color: var(--color-b);
}
}
1
2
3
4
5
CSS.registerProperty({
name: '--animated-color',
syntax: '<color>',
initialValue: 'black'
});

看了代码后,是不是豁然开朗😄。syntax可选值还有lengthnumberimage等等。详情见css-properties-values-api

Paint API

CSS Paint可以在CSS中使用JavaScript编写好的图像。先定义一个Paint Worklet的类,然后使用registerPaint函数注册,最后使用CSS.paintWorklet.addModule()来加载CSS paint worklet文件。

Paint API必须在HTTPS服务器上或者localhost上才能使用。

下面看一个例子,也是来谷歌开发者文档demo结合demo的代码来说明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//paintworklet.js
//这里把原来代码拆分了一下
class myPainter {
static get inputProperties() {
return ['background-color', '--ripple-color', '--animation-tick', '--ripple-x', '--ripple-y'];
}
paint(ctx, geom, properties) {
const bgColor = properties.get('background-color').toString();
const rippleColor = properties.get('--ripple-color').toString();
const x = parseFloat(properties.get('--ripple-x').toString());
const y = parseFloat(properties.get('--ripple-y').toString());
let tick = parseFloat(properties.get('--animation-tick').toString());
if (tick < 0)tick = 0;
if (tick > 1000)tick = 1000;
ctx.fillStyle = bgColor;
ctx.fillRect(0, 0, geom.width, geom.height);
ctx.fillStyle = rippleColor;
ctx.globalAlpha = 1 - tick / 1000;
ctx.arc(
x, y, // center
geom.width * tick / 1000, // radius
0, // startAngle
2 * Math.PI //endAngle
);
ctx.fill();
}
}
registerPaint('ripple', myPainter);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<style>
#ripple.animating {
background-image: paint(ripple);
}
</style>
<body>
<button id="ripple">
Click me!
</button>
</body>
<script>
CSS.paintWorklet.addModule('./index.js');
const button = document.querySelector('#ripple');
let start = performance.now();
let x, y;
document.querySelector('#ripple').addEventListener('click', evt => {
button.classList.add('animating');
[x, y] = [evt.clientX, evt.clientY];
start = performance.now();
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);
})
})
</script>

paintworklet.js中,先定义名为myPainterPaint Worklet,然后把它注册为ripple,然后通过CSS.paintWorklet.addModule方法加载这个Paint Worklet,最后在CSS中使用paint(ripple)

Paint Worklet中的paint()方法中的参数含义如下:

  • ctxPaintRenderingContext2D对象,CanvasCanvasRenderingContext2D对象差不多,是CanvasRenderingContext2D的子集,实现了Canvas大部分功能。
  • geomPaintSize对象,包括元素的宽高信息
  • propertiesStylePropertyMapReadOnly对象,通过在inputProperties中定义返回的属性值,来获取CSS属性值,包括自定义属性。

paint()方法中,还可以有第四个参数,用来获取从CSS中传入的参数。在Paint Worklet中定义inputArguments需要传入什么样的参数。

1
background-image: paint(ripple, red);

1
2
3
static get inputArguments() {
return ['<color>']
}

参考资料