Chrome 65 的新机遇
从 Chrome 65 开始,CSS Paint API(也称为“CSS Custom Paint”或“Houdini 的 Paint Worklet”)默认处于启用状态。Google 优惠是什么?您可以使用它做些什么?它的工作原理是什么?请继续阅读,你会不会...
借助 CSS Paint API,您可以在 CSS 属性需要图片时以编程方式生成图片。background-image
或 border-image
等属性通常与 url()
结合使用以加载图片文件,或与 linear-gradient()
等 CSS 内置函数结合使用。现在,您可以使用 paint(myPainter)
来引用 Paint Worklet,而不是使用它们。
编写 Paint Worklet
如需定义名为 myPainter
的绘制 Worklet,我们需要使用 CSS.paintWorklet.addModule('my-paint-worklet.js')
加载 CSS 绘制 Worklet 文件。在该文件中,我们可以使用 registerPaint
函数注册一个绘制 worklet 类:
class MyPainter {
paint(ctx, geometry, properties) {
// ...
}
}
registerPaint('myPainter', MyPainter);
在 paint()
回调内,我们可以使用 ctx
,使用方式与使用 CanvasRenderingContext2D
(与 <canvas>
中所知的相同)的方式相同。如果您知道如何在 <canvas>
中绘制,则可以在绘制 Worklet 中绘制!geometry
告诉我们可以使用的画布的宽度和高度。properties
我将在本文稍后进行说明。
作为一个入门示例,我们来编写一个棋盘格 Paint Worklet,并将其用作 <textarea>
的背景图片。(我之所以使用文本区域,是因为它默认可调整大小):
<!-- index.html -->
<!doctype html>
<style>
textarea {
background-image: paint(checkerboard);
}
</style>
<textarea></textarea>
<script>
CSS.paintWorklet.addModule('checkerboard.js');
</script>
// checkerboard.js
class CheckerboardPainter {
paint(ctx, geom, properties) {
// Use `ctx` as if it was a normal canvas
const colors = ['red', 'green', 'blue'];
const size = 32;
for(let y = 0; y < geom.height/size; y++) {
for(let x = 0; x < geom.width/size; x++) {
const color = colors[(x + y) % colors.length];
ctx.beginPath();
ctx.fillStyle = color;
ctx.rect(x * size, y * size, size, size);
ctx.fill();
}
}
}
}
// Register our class under a specific name
registerPaint('checkerboard', CheckerboardPainter);
如果您以前使用过 <canvas>
,应该不会对此代码感到熟悉。请点击此处查看实时演示。
![以棋盘图案作为背景图片的文本区域](https://developer.chrome.google.cn/static/blog/paintapi/image/textarea-a-checkerboard-bd94c5b6c6b65.png?authuser=5&hl=lv)
与此处使用通用背景图片的不同之处在于,每当用户调整文本区域的大小时,模式都会按需重新绘制。这意味着背景图片的尺寸始终精确到所需大小,包括针对高密度显示屏的补偿。
这很酷,但同时也是静止的。每次想要相同的模式,但正方形的大小不同时,我们是否要编写一个新的 Worklet?答案是否定的!
将 Worklet 参数化
幸运的是,绘制 Worklet 可以访问其他 CSS 属性,这时额外的参数 properties
就派上用场了。通过为该类提供一个静态 inputProperties
属性,您可以订阅任何 CSS 属性(包括自定义属性)的更改。这些值将通过 properties
参数提供给您。
<!-- index.html -->
<!doctype html>
<style>
textarea {
/* The paint worklet subscribes to changes of these custom properties. */
--checkerboard-spacing: 10;
--checkerboard-size: 32;
background-image: paint(checkerboard);
}
</style>
<textarea></textarea>
<script>
CSS.paintWorklet.addModule('checkerboard.js');
</script>
// checkerboard.js
class CheckerboardPainter {
// inputProperties returns a list of CSS properties that this paint function gets access to
static get inputProperties() { return ['--checkerboard-spacing', '--checkerboard-size']; }
paint(ctx, geom, properties) {
// Paint worklet uses CSS Typed OM to model the input values.
// As of now, they are mostly wrappers around strings,
// but will be augmented to hold more accessible data over time.
const size = parseInt(properties.get('--checkerboard-size').toString());
const spacing = parseInt(properties.get('--checkerboard-spacing').toString());
const colors = ['red', 'green', 'blue'];
for(let y = 0; y < geom.height/size; y++) {
for(let x = 0; x < geom.width/size; x++) {
ctx.fillStyle = colors[(x + y) % colors.length];
ctx.beginPath();
ctx.rect(x*(size + spacing), y*(size + spacing), size, size);
ctx.fill();
}
}
}
}
registerPaint('checkerboard', CheckerboardPainter);
现在,我们可以使用相同的代码绘制所有不同类型的棋盘。更棒的是,我们现在可以进入开发者工具并摆弄值,直到找到正确外观。
不支持绘制 Worklet 的浏览器
在撰写本文时,只有 Chrome 实现了绘制 Worklet。虽然所有其他浏览器供应商都获得了积极的信号,但进展不大。如需随时掌握最新动态,请定期查看 Is Houdini Ready Yet?。在此期间,即使不支持绘制 Worklet,也请务必使用渐进式增强功能,确保代码正常运行。为了确保一切按预期运行,您必须在两个位置调整代码:CSS 和 JS。
您可以通过检查 CSS
对象来检测 JS 中对绘制 Worklet 的支持:
js
if ('paintWorklet' in CSS) {
CSS.paintWorklet.addModule('mystuff.js');
}
对于 CSS 方面,您有两种选择。您可以使用 @supports
:
@supports (background: paint(id)) {
/* ... */
}
一个比较紧凑的技巧是,如果其中包含未知函数,CSS 会让整个属性声明失效,并随后忽略整个属性声明。如果指定某个属性两次 - 第一次没有绘制 worklet,然后指定了绘制 worklet,则会获得渐进式增强:
textarea {
background-image: linear-gradient(0, red, blue);
background-image: paint(myGradient, red, blue);
}
在支持绘制 worklet 的浏览器中,background-image
的第二个声明会覆盖第一个声明。在不支持绘制 worklet 的浏览器中,第二个声明无效且将被舍弃,第一个声明仍然有效。
CSS 绘制 Polyfill
对于许多用途,您还可以使用 CSS 绘制 Polyfill,为现代浏览器添加 CSS 自定义绘制和绘制 Worklet 支持。
使用场景
绘制 Worklet 有很多用例,其中一些比其他用例更明显。其中一种比较明显的方式是使用绘制 worklet 来减小 DOM 的大小。通常情况下,添加元素纯粹是为了使用 CSS 创建装饰。例如,在 Material Design Lite 中,具有涟漪效果的按钮包含 2 个额外的 <span>
元素,用于实现涟漪本身。如果您有很多按钮,这可能会增加大量 DOM 元素,并可能导致应用在移动设备上的性能下降。如果您改为使用绘制 Worklet 来实现涟漪效果,则最终将得到 0 个额外的元素,而只有一个绘制 Worklet。此外,您还可以更轻松地进行自定义和参数化。
使用绘制 Worklet 的另一个好处是,在大多数情况下,使用绘制 Worklet 的解决方案在字节数方面就很小。当然,我们也需要进行权衡:只要画布的大小或任何参数发生变化,绘制代码就会运行。因此,如果您的代码复杂且用时很长,就可能会导致卡顿。Chrome 正在努力将绘制 Worklet 移出主线程,以便即使是长时间运行的绘制 Worklet 也不会影响主线程的响应能力。
在我看来,最令人兴奋的前景是 Paint Worklet 支持对 CSS 功能进行高效的 polyfill 操作,这是浏览器所没有的。例如,对圆锥梯度执行 polyfill 操作,直到它们以原生方式到达 Chrome 为止。再举一个例子:在一次 CSS 会议中,决定现在可以有多种边框颜色。会议仍在进行时,我的同事 Ian Kilpatrick 就使用绘制 Worklet 为这种新的 CSS 行为编写了一个 polyfill。
跳出思维定式
大多数人在了解 Paint Worklet 时,就会开始考虑背景图片和边框图片。绘制 Worklet 的一个不太直观的用例是 mask-image
,它可以使 DOM 元素具有任意形状。例如菱形:
![一个菱形的 DOM 元素。](https://developer.chrome.google.cn/static/blog/paintapi/image/a-dom-element-the-shape-5de28c7a5eff9.png?authuser=5&hl=lv)
mask-image
接受与元素大小相同的图片。遮罩图像透明的区域,元素是透明的。遮罩图片不透明的区域,元素不透明。
现已在 Chrome 中推出
Paint worklet 已在 Chrome Canary 版中推出一段时间。在 Chrome 65 中,该功能默认处于启用状态。快来体验 Paint Worklet 打开的新可能性,并展示你的构建成果!如需更多灵感,请查看 Vincent De Oliveira 系列。