什么是 CSS Hodini

Houdini 是 W3C 新成立的一个任务小组,它的终极目标是实现 css 属性的完全兼容。Houdini 提出了一个前无古人的的设想:开放 CSS 的 API 给开发者,开发者可以通过这套接口自行扩展 CSS,并提供相应的工具允许开发者介入浏览器渲染引擎的样式和布局流程中。

原来 CSS 标准的问题

CSS polyfill 不够强大,无法使用最新的技术标准。

JavaScript 是一种动态语言,它是如此之“动态”以至于有着让人惊叹的可扩展性,所以用 JavaScript 给 JavaScript 打补丁是可以轻松实现的,但 CSS 不是动态的呀。在某些情况下,你可以在构建过程中将一种形式的 CSS 转译成另一种形式(PostCSS 就是一个典型的例子)。而一旦你的补丁是依赖于的 DOM 结构、某一个元素的布局或者它的定位的话,那补丁程序就需要在本地运行了。

不幸的是,要在浏览器中实现这种方案挺难的。

CSS Houdini🚧 - 图1

根据上图浏览器的渲染的过程,在整个渲染过程我们只能控制 DOM,另外 CSSOM 也可以部分控制到,这种程度的暴露是“不确定的、兼容性不稳定的以及缺乏对关键特性的支持的”。CSSOM 是不会告诉你它是如何处理跨源的样式表的,而且对于浏览器无法解析的 CSS 语句它的处理方式就是不解析了。

修改浏览器内部渲染引擎的方案

我们想要“干涉”浏览器解析样式的目的并不仅仅是为了炫技,更是为了框架作者以及开发者们能做到下面两件事情:

  • 统一跨浏览器行为
  • 开发新特性或者给新特性打补丁,让人们可以立刻使用到新特性

Houdini 目前进展

截至2017-11月

为了解决“很难干涉浏览器的渲染过程”的问题,引入新的标准,首次给了开发者介入另外几个渲染环节的权限。下面这张图片展示的是每个环节对应的新标准,开发者可以用这些标准来控制对应的环节。(注意:灰色区块是还在实现中的标准,目前暂时无法使用。)

CSS Houdini🚧 - 图2

CSS Parsing API

允许开发者自由扩展 CSS 词法分析器,引入新的结构(constructs),比如新的媒体规则、新的伪类、嵌套、@extends@apply 等等。只要新的词法分析器知道如何解析这些新结构,CSSOM 就不会直接忽略它们,而是把这些结构放到正确的地方。

CSS 属性与值 API

CSS已经可以自定义属性

  1. :root {
  2. $blue1: #6457d8
  3. }
  4. .box {
  5. color: var($blue1);
  6. }

这个 API 最大的的卖点是,开发者可以在自定义属性上做动画,而仅凭现在的技术,我们是做不到的!

  1. div {
  2. --primary-theme-color: tomato;
  3. transition: --primary-theme-color 1s ease-in-out;
  4. }
  5. div.night-theme {
  6. --primary-theme-color: darkred;
  7. }

这个 API 另一个卖点是注册一个 “apply hook”,也就是开发者可以在 cascade 步骤结束的时候,通过一个钩子更改一个元素的自定义属性值,这个特性对于 polyfills 开发可是很有意义的。

CSS Typed OM

你可以把 CSS Typed OM 视为 CSSOM 2.0,它的目的在于解决目前模型的一些问题,并实现 CSS Parsing APICSS 属性与值 API 相关的特性。

提升性能是 Typed OM 的另一重任。将现在 CSSOM 的字符串值转成有意义的 JS 表达式可以有效的提升性能。

CSS Layout API

开发者可以通过 CSS Layout API 实现自己的布局模块(layout module),这里的“布局模块”指的是 display 的属性值。也就是说,这个 API 实现以后,开发者首次拥有了像 CSS 原生代码(比如 **display:flex****display:table**)那样的布局能力。

让我们来看一个用例,在 Masonry layout library 上大家可以看到开发者们是有多想实现各种各样的复杂布局,其中一些布局光靠 CSS 是不行的。虽然这些布局会让人耳目一新印象深刻,但是它们的页面性能往往都很差,在一些低端设备上性能问题犹为明显。

CSS Layout API 暴露了一个 registerLayout 方法给开发者,接收一个布局名(layout name)作为后面在 CSS 中使用的属性值,还有一个包含有这个布局逻辑的 JavaScript 类。假如你想要用这个方法定义一个 masonry 的类,可以这么写:

  1. registerLayout('masonry', class {
  2. static get inputProperties() {
  3. return ['width', 'height']
  4. }
  5. static get childrenInputProperties() {
  6. return ['x', 'y', 'position']
  7. }
  8. layout(children, constraintSpace, styleMap, breakToken) {
  9. // Layout logic goes here.
  10. }
  11. }

如果上面这个例子你看不明白也用不着担心。关键在下面的代码,当你下载好 masonry.js 后,将它加入你的站点,然后这么来写 CSS 你就能得到一个 masonry 布局的样式了:

  1. body {
  2. display: layout('masonry');
  3. }

CSS Paint API

CSS Paint API 和上面说到的 Layout API 非常相似。它提供了一个 registerPaint 方法,操作方式和 registerLayout 方法也很相似。当想要构建一个 CSS 图像的时候,开发者随时可以调用 paint() 函数,也可以使用刚刚注册好的名字。

下面这段代码展示时如何画一个有颜色的圆型:

  1. registerPaint('circle', class {
  2. static get inputProperties() { return ['--circle-color']; }
  3. paint(ctx, geom, properties) {
  4. // 改变填充颜色
  5. const color = properties.get('--circle-color');
  6. ctx.fillStyle = color;
  7. // 确定圆心和半径
  8. const x = geom.width / 2;
  9. const y = geom.height / 2;
  10. const radius = Math.min(x, y);
  11. // 画圆
  12. ctx.beginPath();
  13. ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
  14. ctx.fill();
  15. }
  16. });

在 CSS 中可以这样使用它:

  1. .bubble {
  2. --circle-color: blue;
  3. background-image: paint('circle');
  4. }

你将在页面上看到一个以蓝色圆形为背景的元素,它的类是 .bubble,这个圆形背景将始终占满 .bubble 元素。

Worklets

这些代码得放在 worklet 脚本中(工作流脚本文件)。

Worklets 的概念和 web worker 类似,它们允许你引入脚本文件并执行特定的 JS 代码,这样的 JS 代码要满足两个条件:第一,可以在渲染流程中调用;第二,和主线程独立。

Worklet 脚本严格控制了开发者所能执行的操作类型,这就保证了性能。

复合滚动和动画

虽然关于 composited scrolling and animation 还没有官方的规范出来,但它可以算是 Houdini 项目中相当广为人知且颇被期待的特性之一。在设想中,这个 API 将会使得开发者能在合成器(compositor)的 worklet (而不是在主线程)中执行程序,还能更改一个 DOM 元素的属性,不过是不会引起渲染引擎重新计算布局或者样式的属性,比如 transformopacity 或者滚动条位置(scroll offset)。

开发者可以通过这个 API 创建高性能的滚动和输入动画,比如滚动头效果、视差效果。你可以在 Github 上看到更多这个 API 试图实现的效果

虽说正式规范还没有确定,但 Chrome 已经在实验性工具中加上了它。事实上 Chrome 的工程师们正在使用这些 API 最终会暴露的语言基元(primitives)来实现 CSS snap pointssticky 定位。这说明了什么?Houdini API 的性能已经足够说服 Chrome 在它之上实现新特性了。单单这一点应该就能说服一直在担心性能问题的的你了吧。
Surma 在 Youtube 上发布了一个模拟 Twitter 应用头部滚动效果的 demo,源码可以在这里查看。

参考

【1】Houdini:CSS 领域最令人振奋的革新
【2】用 CSS Houdini 画一片星空
【3】CSS Houdini & The Future of Styling by Una Kravets | JSConf EU 2019(YouTube 视频)
【4】Houdini | MDN