浅尝 CSS Houdini
前言
近几年,浏览器的特性频出,特别是一些开放浏览器底层控制的API
规范,比如:WebAssembly
,CSS Houdini
,WebGL2.0
等;这些底层API
使得我们有机会定制更多的内容,而且拥有更好的性能,因此也就获得了更多的创造性。
关于CSS Houdini
也是近两年才听的比较多,只知道大概是一个自定义CSS
属性的利器,也没比较详细的去了解,算是比较前沿的CSS
规范了;看过一些案例后,觉得CSS Houdini
确实能够做出一些十分强大的展示和动效,是时候深入了解一下了。
CSS Houdini 现状
上图[1]是截至目前为止(2020-7-11)各浏览器厂商对于CSS Houdini
规范各个模块的支持情况,可以看到目前只有blink
内核(chrome
系)浏览器对于CSS Houdini
有较好的支持,而webkit
内核则是处于开发中,firefox
那就只是有意图去实现,连开发阶段都没有进入;
而且可以看到,即便是CSS Houdini
规范,blink
内核对不同模块的支持程度也不一样;目前Paint API
,Properties & Values API
,Typed OM
完全支持;其他模块不是没有开发进度就是很少的部分特性支持,基本上用处不大;
再看下caniuse网站的统计,移动端那就更加惨烈了,只有安卓原生浏览器和安卓Chrome
浏览器有支持,而且要求版本还比较高(2020-4月后才支持);
所以CSS Houdini
目前支持程度有限,仅在blink
内核浏览器有用武之处,真实应用场景会大打折扣;
CSS Houdini 规范
背景
那么为何要提出CSS Houdini
规范?
首先,因为目前CSS
很多属性在各个内核上可能有表现差异,导致兼容性问题,且原生polyfill
几乎没有可能;
其次,CSS
很多新特性从提案,W3C
纳入规范再到厂商最终实现可能要面临几年以上的时间,这个流程极大地耗费开发者的耐心及应用推广;因此每次出现新特性提案时大多数人都会调侃说到等几年后再试试;
CSS Houdini
规范正是为了解决上述问题而提出的,通过暴露CSS
引擎渲染相关的流程,使得渲染不再是黑盒,而是变得可控,这样样式和渲染的决定权就交到了开发者的手里,也就能解决上述的问题(当然,前提是所有浏览器都支持CSS Houdini
规范……)。
作用
上图[2]解释了CSS Houdini
规范中每个模块具体作用于CSS
渲染的哪个流程,从图中可以看出CSS Houdini
规范的野心很大,试图掌控整个CSS
渲染流程(这对于开发者来说肯定是个好消息);
CSS Parsing API
:用于解析CSS
词法结构,也就是对于渲染中解析器工作的部分;CSS Typed OM
:从CSS Houdini
规范草案可以看出,CSS Typed OM
是CSSOM
的高性能版本,是直接将CSS
值转化为类JS
对象,从而减少中间层的转换;
Converting CSSOM value strings into meaningfully typed JavaScript representations and back can incur a significant performance overhead. This specification exposes CSS values as typed JavaScript objects to facilitate their performant manipulation.[3]
CSS Properties & Values API
:对CSS
自定义属性进行指定值类型等相关配置,指定值类型后自定义属性就能够被应用于动画;CSS Layout API
:用于控制渲染中的布局(Layout
)流程,从而可以自己增加display
类型;CSS Painting API
:用于控制渲染中的绘制(Paint
)流程,对于元素样式达到像素级的控制,且绘制API
是canvas
的子集,方便上手;Worklets
:类似于Web Worker
,是独立于主线程的脚本文件,只能使用指定的API
,专门用于存放注册布局和绘制相关的代码;Composited Scrolling & Animation
:复合滚动和动画API
,目的是在worklets
中支持滚动(位置)及动画变化,使得相应的属性变化不会引起主线程中的重排和重绘,从而拥有高性能的滚动及动画表现;目前blink
内核已经实现了一部分Animation Worklet
[4];
CSS Properties & Values API 简介
这部分的API
很简洁,主要是定义了CSS
自定义属性的一些性质;API
包含CSS.registerProperty()
方法和@property
规则,而这两个本质上作用是一样的,前者是在JS
中使用,后者是在CSS
中使用;
CSS.registerProperty()
CSS.registerProperty(PropertyDefinition)[5]
该方法接收一个配置对象,该对象有以下几个属性:
name
:必填,自定义属性名(注意要包含前面的--
);syntax
:可选,指定自定义属性的语法值类型;可用的值类型可以参考CSS的值与单位 - 学习 Web 开发 | MDN,默认值为"*"
;inherits
:自定义属性值是否可以被继承,默认为false
(不能被继承);initialValue
:可选,自定义属性的初始值;
1 | // 先判断API是否能用 |
上面就是一个注册自定义属性的例子,定义了具体值类型的自定义属性就能够被用于动画了(即可以自动生成关键帧了);CSSUnitValue
是CSS Typed OM
规范中的一个API
,CSS.registerProperty()
对其有依赖;
@property
@property
规则语法和CSS.registerProperty()
类似:
1 | @property --circle-color { |
CSS Painting API 简介
CSS Painting API
主要是由PaintWorklet
组成,PaintWorklet
定义了一套该类型worklet
内所能访问的全局对象(PaintWorkletGlobalScope
),绘制上下文(PaintRenderingContext2D
,是CanvasRenderingContext2D API
的子集)及注册和加载方法;
注册
在worklet
文件(也是JS
文件)内使用registerPaint()
进行注册,该方法接收两个参数:
registerPaint(name, class)[6]
name
:该paintWorklet
的名称,用于在CSS
中进行引用;class
:实现该paintWorklet
的类;
其中类可以实现以下(抽象)成员或方法:
-
static get inputProperties()
:返回一个数组,包含绘制函数所需要的属性(包括自定义属性); -
static get inputArguments()
:返回一个数组,指定传入绘制函数的参数语法类型(类似于registerProperty
方法中的syntax
参数); -
static get contextOptions()
:返回一个配置对象,用于设置绘制上下文(PaintRenderingContext2D
); -
paint(ctx, size, props, args)
:必须实现;绘制函数接收四个函数:ctx
:绘制上下文,即当前paintWorklet
对应的PaintRenderingContext2D
实例;size
:PaintSize
对象,包含width
和height
两个属性,即paintWorklet
作用的元素的尺寸信息;props
:StylePropertyMapReadOnly
对象,只能访问由inputProperties
属性返回的属性;args
:参数数组,可以接收由CSS paint()
函数传入的参数;
关于 StylePropertyMapReadOnly 对象
该对象类似于Map
对象,但是不能set
,因此是只读的;通过get()
方法可以获取到对应属性,不过得到并不是属性值,而是一个CSSStyleValue
对象(属于CSS Typed OM
),不同syntax
类型的属性会有不同类型的CSSStyleValue
对象;
不过一般来说数值类型的属性值可以通过value
属性来获取值,颜色类型则通过toString()
方法来获取值,如:
1 | const boxColor = props.get('--box-color').toString() |
加载
事实上,worklet的加载方式很类似,都是通过addModule(url)
方法来进行:
1 | // 先判断paintWorklet是否可用 |
注:worklet
文件地址仅支持https
和localhost
方式!
使用
paintWorklet
加载后就能够在CSS
中进行使用了,可以使用paint(name)
函数来启用worklet
,name
就是注册时传入的名称;
name
不需要加上引号!paint()
函数返回值类型为<image>
,因此只能在属性值类型为<image>
的属性中进行使用!如:background-imgae
,list-style-image
等;更多关于<image>
类型的信息可以参考- CSS(层叠样式表) | MDN ;
传参
paint()
函数也能传参,然后被paintWorklet
中的paint()
方法所接收:
1 | paint(paint-name, arg1, arg2, ...) |
一个案例
看到上面这个动画,在不使用CSS Houdini
之前,很难想象纯CSS
能够得到这样的效果;
相关文档
- 用CSS Houdini画一片星空 - 掘金
- Houdini:CSS 领域最令人振奋的革新 - 知乎
- CSS Houdini | MDN
- Using the CSS properties and values API - Web APIs | MDN
- Houdini工作小组关于CSS的揭秘 - w3ctech
- 和 Houdini, CSS Paint API 打个招呼吧 - 知乎:
CSS Paint API
入门体验 - StylePropertyMapReadOnly - Web APIs | MDN
- CSS Object Model (CSSOM) - Web APIs | MDN:
CSS Typed OM
一览 - CSS Painting API - Web APIs | MDN:
CSS Painting API
demo