CSS Paint API是W3C规范中之一,目前的版本是Level1。它也被称为CSS Custom Paint或者Houdini's Paint Worklet。对于开发者而言,有一个值得高兴的是,Chrome65将会支持该API。也就是说,可以使用CSS Paint API提供的registerPaint(name, paintCtor)
做一些事情。
那么CSS Paint API是什么?你能用它做什么?它又是如何工作的呢?带着一系列的为什么,我们开启对CSS Paint API的初探。
什么是CSS Paint
先来了解CSS Paint是什么?在理解这个概念之前,我们先来回忆一下,我们在平时写CSS时是如何给一个元素设置背景图片。了解CSS的同学,都应该知道,采用background-image
属性,比如:
background-image: url(xxx.jpg)
或者:
background-image: linear-gradient(to bottom, red, green)
除了给background-image
指定一个图片之外,还可以是渐变(CSS中的渐变相当于一张背景图片)。而我们要了解的CSS Paint则是通过JavaScript的方式,让你在CSS中能够引入用JavaScript编写的图形。感觉有点类似于HTML5中的canvas
,对吗?如果你继续往后看,你会越加有这样的感觉。
写一个Paint Worklet
先定义一个叫作myPainter
的Paint Worklet,接下来使用CSS.paintWorklet.addModule('my-paint-worklet.js')
来加载已定义好的CSS Paint Worklet。在my-paint-worklet.js
文件中,使用registerPaint
函数来注册一个Paint Woklet的类:
class MyPainter {
paint(ctx, geometry, properties) {
// ...
}
}
registerPaint('myPainter', MyPainter)
在paint()
回调中,我们可以使用<canvas>
中CanvasRenderingContext2D
的ctx
方法。如果你熟悉<canvas>
,那么你就可以知道怎么绘制Paint Worklet。geometry
用来指定画布的width
和height
,properties
可以获取自定义元素属性,这么说有点抽象,后面会介绍到该属性。
特别声明:CSS Paint中的Paint Worklet的
ctx
和<canvas>
中的ctx
并不是百分之百的相同。到目前为止,文本渲染的方法是无法使用的,这主要是出于安全原因,你无法从画布上读取像素。
来看一个简单的示例。先创建一个index.html
文件:
<!doctype html>
<html>
<head>
<style>
body {
width: 100vw;
height: 100vh;
background-image: paint(checkerboard);
}
</style>
<script>
CSS.paintWorklet.addModule('checkerboard.js')
</script>
</head>
<body>
</body>
</html>
然后创建checkerboard.js
,并在这个文件中添加下面的代码:
class CheckerboardPainter {
paint(ctx, geom, properties) {
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();
}
}
}
}
registerPaint('checkerboard', CheckerboardPainter);
到时将会看到的效果如下所示:
上面的示例,先定义了一个叫CheckboardPainter
的Paint Worklet,并且将之注册为checkboard
,然后通过CSS.paintWorklet.addModule()
的方法加载这个Paint Worklet。最后在CSS中,使用paint(checkboard)
给指定的元素添加背景图。其最终效果正如上图所示。
可能你会纳闷,这样的效果使用background-image
就能实现(不管是调用图片,还是使用linear-gradient
都可以实现类似效果)。那他们两者之间有何区别吗?其实两者是有所区别的:
CSS Paint与
background-image
的差别就是background-image
是根据代码计算出来的,不会随着元素的大小变化而伸缩。而CSS Paint绘制的图像总是会和元素容器所需保持一样的大。也就是说,让你修改元素大小可视区域时,CSS Paint绘制的图像会重新绘制。言外之意,背景图像总是和它所需要的一样大,包括对高密度(Hight-density)显示器的补偿。
是不是很酷。
个性化Paint Worklet
前面提到过,定义一个Paint Worklet时paint()
方法接受三个参数,其中第三个参数properties
可以让Paint Worklet可以访问其他CSS属性,这就是properties
参数的强大之处。通过一个静态的类,比如inputProperties
属性,你可以对任何CSS属性,包括自定义属性进行更改。这些值将通过properties
参数提供给你。
比如下面这个例子。同样先创建一个index.html
文件,在文件中加入下面的代码:
<!doctype html>
<html>
<head>
<style>
body {
width: 100vw;
height: 100vh;
--checkerboard-spacing: 10;
--checkerboard-size: 32;
background-image: paint(checkerboard);
}
</style>
<script>
CSS.paintWorklet.addModule('checkerboard.js')
</script>
</head>
<body>
</body>
</html>
然后在创建的checkerboard.js
文件中加入下面的代码:
class CheckerboardPainter {
static get inputProperties() {
return [
'--checkerboard-spacing',
'--checkerboard-size'
]
}
paint(ctx, geom, properties) {
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);
现在我们可以使用相同的代码来处理所有不同类型的格子效果。但更爽的是,现在可以通过开发者调试工具,在找到正确的外观之前,对这些值进行修改。
在 CheckerboardPainter
类中,给静态属性 inputProperties
定义了两个属性:--checkerboard-spacing
和 --checkerboard-size
,这两个属性可以像一般的 CSS 的属性一样使用于 HTML 元素,而这两个属性的值将被 paint()
的第三个参数 properties
获得,被用于生产图像。所以,如果修改 body
的 --checkerboard-spacing
或者 --checkerboard-size
属性,背景图将会发生改变,正如上面的录屏展示的效果一样。
注意:如果把颜色也参数化是不是很有意?
spec
允许paint()
函数获取参数列表。这个特性还没有在Chrome中实现,因为它目前还严重依赖于Houdini的属性和值的API,让其能正常工作,还需要一些时间。
不支持Paint Worklet的浏览器
到目前为止,只有Chrome浏览器支持Paint Worklet(Chrome 65可以看到效果)。尽管其他浏览器都发出响应将会支持CSS Paint API的特性,但到目前依旧没有啥进展。为了跟上时代的发展,Houdini支持吗?与此同时,就算浏览器还不支持CSS Paint的Paint Worklet,我们也要确保使用渐进增加来保证你的代码能正常运行。为了确保这一点,你必须在CSS和JavaScript中调整你的代码。
如果你从未接触过Houdini,甚至说都不知道他是什么?建议你点击这里对Houdini进行一些简单的了解。另外这里有一个CSS Houdini的示例仓库,这些示例将向你展示Houdini的神奇之处。
回到正题上来吧,对于不支持的浏览器,可以通过检查CSS对象,实现对JavaScript中Paint Worklet支持情况做一个检测:
if ('paintWorklet' in CSS) {
CSS.paintWorklet.addModule('mystuff.js');
}
而在CSS通过@supports
来做相应的检测:
@supports (background: paint(id)) {
body {
background-image: paint(checkerboard);
}
}
另外,众所周知,如果浏览器遇到一个未知的属性,则会忽略此属性声明的规则。如果你对同一个属性进行两次声明,前者是CSS的属性,其后紧跟Paint Worklet,你就可以对不支持CSS Paint的浏览器做降级处理。比如下面这样:
body {
background-image: linear-gradient(0, red, blue);
background-image: paint(myGradient, red, blue);
}
这样一来,支持CSS Paint的浏览器,第二个background-image
将会覆盖第一个。在不支持CSS Paint的浏览器,第二个属性规则将会示为无效,而第一个background-image
将会起作用。
示例
在众多CSS Paint的示例中,其中一些比其他的示列更易理解。其中一个比较易于理解的示例就是使用CSS Paint来减少对DOM元素。通常,元素的添加纯粹是为了使用CSS创建修饰。例如,在Material Design Lite中的button
的Ripple效果。为了实现这个效果,添加了两个额外的<span>
元素。如果你的Web页面有很多这样的按钮效果,那么你就要增加很多个DOM元素,因此可能影响你的页面性能。如果使用CSS Paint来实现Ripple效果,你不需要添加任何额外的DOM元素。此外,你还拥有更易于自定义的属性。
使用CSS Paint的另一个好处是,在大多数的情况下,能解决很多CSS无法解决的事情,而且代码量也少。当然,其中也有一个取舍问题。当画布的大小或任何参数发生变化时,绘图的代码将会运行。因此,如果你的代码很复杂,那需要很长的时间,它可能会引入jank。Chrome正在处理主要线程上的Paint Worklet,因此即使运行时间长,Paint Worklet也不会影响主线程的响应能力。
对于我来说,最令人兴奋的前景是,CSS Paint可以很快的为CSS新特性创建Polyfill。比如conic-gradient
的Polyfill。另外的一个例子,在CSS会议中,它决定你现在可以有多个边框颜色。在这个会议还在进行之时,@Kilpatrick就通过CSS Paint为其写了一个对应的Polyfill。
不规则盒子的思考
大多数人在学习CSS Paint时都从background-image
和border-image
着手。其实另一个值得我们思考的是,如果使用 CSS Paint的mask-image
可以让DOM元素具有任意形状。比如钻石形状:
写在最后
CSS Paint在Chrome Canary中已有一段时间。值得庆幸的是,在Chrome 65中将会默认启用CSS Paint。对于开发者而言,我们应该不断去尝试新的可能性,CSS Paint将会让我们打开更多的思路,为你的灵感提供更多于创作的空间。可以看看@Vincent De Oliveira收藏的一些案例。或许在这些案例中,你也能产生一些新的灵感。
特别声明:此篇文章内容来源于@Surma的《CSS Paint API》一文。
如需转载,烦请注明出处:https://www.w3cplus.com/css/css-paint-api.html