在上一篇文章中 -- 现代 CSS 之高阶图片渐隐消失术[1],我们借助了 CSS @Property 及 CSS Mask 属性,成功的实现了这样一种图片渐变消失的效果:,
,CodePen Demo -- 基于 @property 和 mask 的文本渐隐消失术[2]。,但是,这个效果的缺陷也非常明显,虽然借助了 SCSS 简化了非常多的代码,但是,如果我们查看编译后的 CSS 文件,会发现,在利用 SCSS 只有 80 的代码的情况下,编译后的 CSS 文件行数高达 2400+ 行,实在是太夸张了。,
,究其原因在于,我们利用原生的 CSS 去控制 400 个小块的过渡动画,控制了 400 个 CSS 变量!代码量因而变得如此之大。,那么,如何有效的降低代码量呢?,又或者说,在今天,是否 CSS 还存在着更进一步的功能,能够实现更为强大的效果?,没错,是可以的,这也就引出了今天的主角,CSS Houdini 之 CSS Paint API。,首先,什么是 CSS Houdini?,Houdini 是一组底层 API,它们公开了 CSS 引擎的各个部分,从而使开发人员能够通过加入浏览器渲染引擎的样式和布局过程来扩展 CSS。Houdini 是一组 API,它们使开发人员可以直接访问 CSS 对象模型[3] (CSSOM),使开发人员可以编写浏览器可以解析为 CSS 的代码,从而创建新的 CSS 功能,而无需等待它们在浏览器中本地实现。,而 CSS Paint API 则是 W3C 规范中之一,目前的版本是 CSS Painting API Level 1[4]。它也被称为 CSS Custom Paint 或者 Houdini's Paint Worklet。,简单来说人话,CSS Paint API 的优缺点都很明显。,当然,优点看着很美好,缺点也很明显,CSS Paint API 的缺点:,CSS Houdini[5] 的特性就是 Worklet (en-US)[6]。在它的帮助下,你可以通过引入一行 JavaScript 代码来引入配置化的组件,从而创建模块式的 CSS。不依赖任何前置处理器、后置处理器或者 JavaScript 框架。,废话不多说,我们直接来看一个最简单的例子。,先看看最终的结果:,
,看似有点点复杂,其实非常好理解。仔细看我们的 CSS 代码,在 background 赋值的过程中,没有直接写具体颜色,而是借助了一个自定义了 CSS Houdini 函数,实现了一个名为 drawBg 的方法。从而实现的给 Div 上色。,registerPaint 是以 worker 的形式工作,具体有几个步骤:,上面的步骤搞明白后,核心的逻辑,都在我们自定义的 drawBg 这个方法后面定义的 class 里面。CSS Painting API 非常类似于 Canvas,这里面的核心逻辑就是:,而我们上面 DEMO 做的事情也是如此,获取到 CSS 传递进来的 CSS 变量的值。然后,通过 ctx.fillStyle 和 ctx.fillRect 完成整个背景色的绘制。,OK,了解了上面最简单的 DEMO 之后,接下来我们尝试稍微进阶一点点。利用 registerPaint 实现一个 circleBgSet 的自定义 CSS 方法,实现类似于这样一个背景图案:,
,CodePen Demo -- CSS Hudini Example - Background Circle[7]。,首先,我们还是要在 HTML 中,利用 CSS.paintWorklet.addModule('') 注册引入我们的 JavaScript 文件。,其次,在 CSS 中,我们只需要在调用 background 属性的时候,传入我们即将要实现的方法:,可以看到,核心在于 background: paint(circleBgSet),我们将绘制背景的工作,交给接下来我们要实现的 circleBgSet 方法。同时,我们定义了 3 个 CSS 变量,它们的作用分别是:,好了,接下来,只需要在 JavaScript 文件中,利用 CSS Painting API 实现 circleBgSet 方法即可。,来看看完整的 JavaScript 代码:,代码其实也不多,并且核心的代码非常好理解。这里,我们再简单的解释下:,
,这里,其实 CSS Houdini 的画布 API 是 Canvas API 的是一样的,具体存在这样一些映射,我们在官方规范 CSS Painting API Level 1 - The 2D rendering context[8] 可以查到:,
,还记得我们上面传入了 3 个 CSS 变量吗?这里我们只需要简单改变上面的 3 个 变量,就可以得到不一样的图形。让我们试一试:,
,又或者:,
,有了上面的铺垫,下面我们开始实现我们今天的主题,利用 registerPaint 自定义方法还原实现这个效果,简化 CSS 代码量:,
,自定义的 paint 方法,不但可以用于 background,你想得到的地方,其实都可以。,因此,这里,我们利用 CSS Houdini 的 registerPaint 实现自定义的 mask 属性绘制。,首先,还是一样,HTML 中需要引入一下定义了 registerPaint 方法的 JavaScript 文件:,首先,我们会实现一张简单的图片:,效果如下:,
,当然,我们的目标是利用 registerPaint 实现自定义 mask,那么需要添加一些 CSS 代码:,这里,我们 mask: paint(maskSet) 表示使用我们自定义的 maskSet 方法,并且,我们引入了两个 CSS 自定义变量 --size-m 和 --size-n,表示我们即将要用 mask 属性分隔图片的行列数。,接下来,就是具体实现新的自定义 mask 方法。当然,这里我们只是重新实现一个 mask,而 mask 属性本身的特性,透明的地方背后的内容将会透明这个特性是不会改变的。,JavaScript 代码:,这一段代码非常好理解,我们做的事情就是拿到两个 CSS 自定义变量 --size-n 和 --size-m 后,通过一个双循环,依次绘制正方形填满整个 DIV 区域,每个小正方形的颜色为带随机透明度的黑色。,记住,mask 的核心在于,透过颜色的透明度来隐藏一个元素的部分或者全部可见区域。因此,整个图片将变成这样:,
,当然,我们这个自定义 mask 方法也是可以用于 background 的,如果我们把这个方法作用于 backgorund,你会更好理解一点。,实际的图片效果是这样:,
,好,回归正题,我们继续。我们最终的效果还是要动画效果,Hover 的时候让图片方块化消失,肯定还是要和 CSS @property 自定义变量发生关联的,我们简单改造下代码,加入一个 CSS @property 自定义变量。,这里,我们引入了 --transition-time 这个变量。接下来,让他在 maskSet 函数中,发挥作用:,这里,与上面唯一的变化在于这一行代码:ctx.fillStyle = "rgba(0,0,0," + (t * (Math.random() + 1)) + ")"。,对于每一个小格子的 mask,我们让他的颜色值的透明度设置为 (t * (Math.random() + 1)):,看看最终的效果:,
,CodePen Demo -- CSS Hudini Example[9]。,是的,细心的同学肯定会发现,文章一开头给的 DEMO 是切分了 400 份 mask 的,而我们上面实现的效果,只用了 100 个 mask。,这个非常好解决,我们不是传入了 --size-n 和 --size-m 两个变量么?只需要修改这两个值,就可以实现任意格子的 Hover 渐隐效果啦。还是上面的代码,简单修改 CSS 变量的值:,结果如下:,
,CodePen Demo -- CSS Hudini Example[10]。,到这里,还有一个小问题,可以看到,在消失的过程中,整个效果非常的闪烁!每个格子其实闪烁了很多次。,这是由于在过渡的过程中,ctx.fillStyle = "rgba(0,0,0," + (t * (Math.random() + 1)) + ")" 内的 Math.random() 每一帧都会重新被调用并且生成全新的随机值,因此整个动画过程一直在疯狂闪烁。,如何解决这个问题?在这篇文章中,我找到了一种利用伪随机,生成稳定随机函数的方法:Exploring the CSS Paint API: Image Fragmentation Effect[11]。,啥意思呢?就是我们希望每次生成的随机数都是都是一致的。其 JavaScript 代码如下:,我们利用上述实现的随机函数 random() 替换掉我们代码原本的 Math.random(),并且,mask 小格子的 ctx.fillStyle 函数,也稍加变化,避免每一个 mask 矩形小格子的渐隐淡出效果同时发生。,修改后的完整 JavaScript 代码如下:,还是上述的 DEMO,让我们再来看看效果,分别设置了不同数量的 mask 渐隐消失:,
,CodePen Demo -- CSS Hudini Example & Custom Random[12]。,Wow!修正过后的效果不再闪烁,并且消失动画也并非同时进行。在 Exploring the CSS Paint API: Image Fragmentation Effect[13] 这篇文章中,还介绍了一些其他利用 registerPaint 实现的有趣的 mask 渐隐效果,感兴趣可以深入再看看。,这样,我们就将原本 2400 行的 CSS 代码,通过 CSS Painting API 的 registerPaint,压缩到了 50 行以内的 JavaScript 代码。,当然,CSS Houdini 的本事远不止于此,本文一直在围绕 background 描绘相关的内容进行阐述(mask 的语法也是背景 background 的一种)。在后续的文章我将继续介绍在其他属性上的应用。,那么,CSS Painting API 的兼容性到底如何呢?,CanIUse - registerPaint[14] 数据如下(截止至 2022-11-23):,
,Chrome 和 Edge 基于 Chromium[15] 内核的浏览器很早就已经支持,而主流浏览器中,Firefox 和 Safari 目前还不支持。,CSS Houdini 虽然强大,目前看来要想大规模上生产环境,仍需一段时间的等待。让我们给时间一点时间!,好了,本文到此结束,希望本文对你有所帮助 :),[1]现代 CSS 之高阶图片渐隐消失术: https://github.com/chokcoco/cococss/issues/23。,[2]CodePen Demo -- 基于 @property 和 mask 的文本渐隐消失术: https://codepen.io/Chokcoco/pen/qBKPgZY。,[3]CSS 对象模型: https://developer.mozilla.org/zh-CN/docs/Web/API/CSS_Object_Model。,[4]CSS Painting API Level 1: https://drafts.css-houdini.org/css-paint-api/#paintworkletglobalscope。,[5]CSS Houdini: https://developer.mozilla.org/zh-CN/docs/Web/Guide/Houdini。,[6]Worklet (en-US): https://developer.mozilla.org/en-US/docs/Web/API/Worklet。,[7]CodePen Demo -- CSS Hudini Example - Background Circle: https://codepen.io/Chokcoco/pen/abKExxN。,[8]CSS Painting API Level 1 - The 2D rendering context: https://drafts.css-houdini.org/css-paint-api/#paintworkletglobalscope。,[9]CodePen Demo -- CSS Hudini Example: https://codepen.io/Chokcoco/pen/KKeQWJb。,[10]CodePen Demo -- CSS Hudini Example: https://codepen.io/Chokcoco/pen/oNyEpLN。,[11]Exploring the CSS Paint API: Image Fragmentation Effect: https://dev.to/this-is-learning/exploring-the-css-paint-api-image-fragmentation-effect-3ekl。,[12]CodePen Demo -- CSS Hudini Example & Custom Random: https://codepen.io/Chokcoco/pen/eYKVQGG。,[13]Exploring the CSS Paint API: Image Fragmentation Effect: https://dev.to/this-is-learning/exploring-the-css-paint-api-image-fragmentation-effect-3ekl。,[14]CanIUse - registerPaint: https://caniuse.com/?search=registerPaint。,[15]Chromium: https://www.google.com.hk/search?newwindow=1&rlz=1C5GCEM_enCN988CN988&q=Chromium&spell=1&sa=X&ved=2ahUKEwi3he2ensL7AhVaSmwGHdnzBxgQkeECKAB6BAgoEAE。
© 版权声明
文章版权归作者所有,未经允许请勿转载。