微信号:w3cplus_12

介绍:W3CPLUS是一个前端爱好者的家园,W3CPLUS努力打造最优秀的web 前端学习的站点.

CSS Paint API

2018-01-31 00:08 大漠

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>CanvasRenderingContext2Dctx方法。如果你熟悉<canvas>,那么你就可以知道怎么绘制Paint Worklet。geometry用来指定画布的widthheightproperties可以获取自定义元素属性,这么说有点抽象,后面会介绍到该属性。

特别声明: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-imageborder-image着手。其实另一个值得我们思考的是,如果使用 CSS Paint的mask-image可以让DOM元素具有任意形状。比如钻石形状:

写在最后

CSS Paint在Chrome Canary中已有一段时间。值得庆幸的是,在Chrome 65中将会默认启用CSS Paint。对于开发者而言,我们应该不断去尝试新的可能性,CSS Paint将会让我们打开更多的思路,为你的灵感提供更多于创作的空间。可以看看@Vincent De Oliveira收藏的一些案例。或许在这些案例中,你也能产生一些新的灵感。

特别声明:此篇文章内容来源于@Surma的《CSS Paint API》一文。


文章涉及到图片和代码,如果展示不全给您带来不好的阅读体验,欢迎点击文章底部的 阅读全文。如果您觉得小站的内容对您的工作或学习有所帮助,欢迎关注此公众号。





W3cplus.com

————————————

记述前端那些事,引领web前沿


长按二维码,关注W3cplus



 
W3cplus 更多文章 使用SVG制作进度条之二 如何在Vue项目中使用vw实现移动端适配 用SVG和Vanilla JS框架创建一个“星形变心形”的动画效果 使用SVG制作进度条 SVG之旅:线条之美,玩转SVG线条动画
猜您喜欢 第2期-12大github上精选的PHP相关项目 21天混入数据科学家队伍(上) 理解Linux load average的误区 【2015微软学生夏令营】体验骇客精神,放飞青春创想 八卦安卓神器 dSploit/cSploit/zANTI