[关闭]
@zhouweicsu 2016-08-22T19:44:32.000000Z 字数 5687 阅读 907

Houdini 分享


CSS


大家好,我做一个自我介绍,我叫周炜,来自奇舞团,目前支持商业产品团队。
今天分享的主题是 Houdini: A Revolution of CSS
为什么说这是一场 CSS 革命,大家觉得 CSS


Houdini 是 W3C 新成立的一个小组,它的终极目标是实现 CSS 属性的完全兼容。
这个工作小组聚齐了(来自Google, Mozilla,Apple,Opera,Microsoft,HP,Intel) 各大主流浏览器厂商的工程师,小组的工作就是把浏览器中 CSS 引擎的 API 开放给开发者,开发者可以通过这套接口自行扩展 CSS, 并提供相应的工具允许开发者介入浏览器渲染引擎的样式和布局流程中。
为什么叫 Houdini 这个名字?Houdini 是美国一个伟大的魔术师,擅长逃脱术,就是把手脚困住丢入一个浸满水的封闭空间里,或者绑在柱子上旁边放一桶炸药,然后逃出来之类的那种,CSS 这个小组叫这名字,就是想让 CSS 从浏览器的束缚种挣脱出来,接下来我们就讲讲,Houdini 是如何让 CSS 焕然一新的。


每次我要实现一个炫酷的效果,我就想用用 CSS 的新特性,我首先就得打开 Can I Use 这个网站,查一查各大浏览器支不支持,一看,诶,IE 10 都不支持,没法用是不是?更糟糕的情况是,所有浏览器都支持这种特性,但表现却不一样,比如 placeholder。


同样的情况,我想要使用 JavaScript 的一个新特性,我可以使用 Babel,ECMAScript 6 的特性都可以用了,你甚至还可以用上 ECMAScript 7 的新特性,比如 async 和 await。
实在不行,你还可以自己写 polyfill。


那如果我想新增一个 CSS 特性呢?就目前的 CSS 现状,每一份特性草案都需要许多年才能被广泛应用。我们可以看一下这个流程图,首先有人说,诶,我想新增一个 CSS 特性,写一份 W3c 的规范,浏览器厂商实现,在形成规范文档和浏览器实现的过程中,出点什么问题就等,最后得到浏览器采纳之后,你才可以用,这个过程一般都需要好几年。


我们再看看新增一个 JavaScript 特性需要多久。写一个 polyfill 就能搞定,复杂的几天,简单一点的几个小时,如果你像月影那么牛逼,那就是分分钟的事。


那是什么原因让 CSS 和 JavaScript 的差异这么大呢?


这就得从浏览器的工作原理说起了,我们在这里简单介绍一下主要流程。一开始浏览器会从服务器获取资源,我们现在只考虑 HTML 和 CSS 这两种情况,一开始浏览器解析 HTML 文档,将各个 tag 逐个转化成 DOMTree 上的节点,同时也会解析外部 CSS 文件以及 style 标签中的样式数据,生成 CSS OM(Object Model),然后生成 Render Tree,Render Tree 中的节点就是包含颜色和尺寸等视觉属性的矩形,想想 CSS 中的盒模型的盒子,这些矩形的排列顺序就是在浏览器窗口中显示的顺序。进入页面 Layout 的阶段,浏览器会为每个矩形计算出在屏幕上的准确坐标。一切就绪,浏览器就会进入 Paint 阶段,这个阶段本质上就是填充像素,一般情况下 Paint 并非总是在内存中的单层画面里完成的,在必要时将会把一帧画面绘制成多层画面,然后将这若干层画面合并成一张图片显示到屏幕上。合并渲染层的过程就属于 Composite 这一块的内容。Composite 这一块在页面的性能优化方面很重要。这些步骤都完成了,页面也就展现在浏览器中了。
上面就是浏览器工作的基本原理的一个简单介绍。


下面我们来看看在浏览器工作的整个过程中,我们能控制的部分只有 DOM 和 CSS OM 这两个环节,我们很难干涉浏览器的渲染过程。从 Layout 到 Display 这一部分我们都无能为力。如果你想支持某个自己写的 CSS 属性,


如何衔接 可扩展的 Web 宣言 这一块内容?

讲到这里,问一下大家,有多少听说过这个 The Extensible Web Manifesto?可扩展的 Web 宣言倡导暴露底层 API,让高层 API 的构建与浏览器解耦。这样开发人员才能更快地实现新技术,与此同时又为我们掌控 Web 技术与日俱增的复杂性提供了更好的方案。


Houdini 就是充分响应了这个宣言,Houdini 就是可扩展的 CSS,有了 Houdini 你可轻松新增 CSS 特性,而不需要等 W3C 完善规范文档,等各大浏览器支持,无需等待。有了 Houdini,你的页面将会变得更快更流畅,因为 Houdini 将浏览器底层的一系列 API 都暴露给我们开发者,提供了之前我们无法干涉的 Layout,Paint,Composite 过程中的API。目前计划中的有 9 个 API。


我们可以通过下面这张图看一下这些 API 与浏览器工作阶段的对应关系,具体每个 API 的信息我会在后面详细讲。

CSS 解析过程涉及 3 个 API,一个是 CSS Parser API,允许开发者自由扩展 CSS 词法分析器。一个是 CSS Typed OM,这个的前身就是 CSS OM。CSS Properties and Values API,这个 API 让自定义 CSS 特性成为可能。后面我们会讲到这个语法。

CSS Layout API 对应的就是浏览器布局阶段, CSS Painting API 对应 浏览器渲染过程, Composited Scrolling And Animation 对应的合成层阶段,这些 API 都需要基于 Worklets,这个Worklets 类似于 Web Worker,是上面这三个 API 执行的基础。

另外两个是 Box Tree API 和 Font Mertics API, Box Tree API 对应的是浏览器的布局阶段,前面在介绍浏览器的基本工作原理中提到过矩形,就是这些 Box。Font Mertics API 是字体相关的 API,对应的 Layout 和 Paint 过程。

从这个图中我们可以看出,以前我们无法接触到的解析,布局,渲染,合并等阶段,Houdini 全部都暴露了出来。这是 CSS 历史上很重要的一步。


目前 W3C 公布了 4 份工作草案,分别是Type OM,Properties and Vaules API,Painting API 和 Worklets。接下来我们就逐一介绍每份草案的基本情况。


我们还是以浏览器的工作原理为顺序,从前往后讲。首先是 CSS Parser API,我们知道目前浏览器解析 CSS 的时候,如果是它不认识的规则,就会直接忽略,CSS Parser API 则允许我们自由扩展 CSS 词法分析器,引入新的浏览器不认识的结构,比如新的媒体规则、新的伪类、嵌套、@extends、@apply 等等。目前这个 API 还没有被写入规范,所以今天我讲的内容随时都会有变化。
这个地方有个疑问:CSS 规则中比如新增一个 mushroom:2px; 这个 mushroom 是属于 CSS Parser API 还是 Properties an Values 呢?上面提到的媒体规则、新的伪类、嵌套等等这些,对于 Parser API 和 P&V 来说区别在哪里?


CSS Parser API 的之后我们得到的就是 CSS Typed OM,它的前身是 CSS Object Model,我们知道 CSS OM 中处理的一切规则都是基于字符串的,而在 Typed OM 中,我们将字符串转换成 JavaScript 中的类型表达式,我们看一下这个例子,我们在使用 JS 处理 CSS 中的数学计算,先取到高度的字符串 100px,然后去掉 px 这个单位得到字符串 100,在 parseInt 然后加上 100 得到 200,最后在加上单位 px 然后设置回去。这是我们对 CSS 的操作,然后在浏览器解析的时候,还得再解析 100px 这个字符串,转换成数字。这个过程从字符串到数字,再到字符串,然后又转数字,可以看到这个过程简直 ugly;


Typed OM 通过将解析字符串改为解析 StylePropertyMap 之后,我们看一下代码是怎么写的。

Instead of strings you will be working on an element’s StylePropertyMap, where each CSS attribute has it’s own key and corresponding value type. Attributes like width have LengthValue as their value type. A LengthValue is a dictionary of all CSS units like em, rem, px, percent, etc. Setting height: calc(5px + 5%) would yield a LengthValue{px: 5, percent: 5}. Some properties like box-sizing just accept certain keywords and therefore have a KeywordValue value type. The validity of those attributes could then be checked at runtime.

据 Chrome 的工程师 Surma 称,目前 CSS Typed OM 在 Chrome Canary 上面已经基本上完成了,相信不久我们就能看到 demos 了。


CSS 已经有自定义属性了,CSS Properties and Values API 的出现进一步推动了自定义属性,它还允许自定义属性添加不同的类型,大大增强了自定义属性的能力。


我们看一下这个 API 的语法,name syntax inherits initialValue 分别的意义。在注册了这个属性之后,我们看一下 CSS 中时如何使用的。前两行代码的语法和现有的自定义属性的写完完全一致,如果只有前两行代码,用现有的技术都可以实现,但在现在的技术下浏览器无法识别 transition 后面的 --stop-color, 而这个 API 可以做到,也就是说现在我们可以在自定义属性上做动画,是这个 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 的类,可以这么写:

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


CSS Painting API


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

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

虽说正式规范还没有确定,但 Chrome 已经在实验性工具中加上了它。事实上 Chrome 的工程师们正在使用这些 API 最终会暴露的语言基元(primitives)来实现 CSS snap points 和 sticky 定位。这说明了什么?Houdini API 的性能已经足够说服 Chrome 在它之上实现新特性了。单单这一点应该就能说服一直在担心性能问题的的你了吧。


Worklets
在前面我们提到过的 Paint API 中,我们有看到 window.paintWorklet,Paint API、 Layout API、
Composited Scrolling And Animation 这些都是基于 worklets 的。

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

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


Box Tree API

Box 就是 CSS 盒模型中的盒子,这些盒子可以控制里面的内容如何展现和具体位置,Box Tree 就是这些盒子的层级关系树,跟 DOM 树类似,区别就是当这个盒子的 position 为 absolute、fixed 这种特殊值时,层级关系会不一样。Box Tree API 就是访问这些盒子的基本信息的 API,比如盒子的宽高,top left 值,盒子对应的 DOM 节点等等。


Font Metrics API

看标题就知道这个 API 是关于字体的。比如我要渲染字符串 X,使用的是字体 Y,字体大小的是 Z,这个时候我的字体所占的宽高是多少?可以通过这个 API 获取,这个一般在自定义的 Layout, 或者特殊文本布局这些需要计算文本宽高的情况下使用。


以上就是这 9 个 API 的基本介绍。目前来说只有 Painting API 可以自己写代码玩一玩,其他都还在实现阶段,有的规范文档都还未完善。除了这 9 个 API,Houdini 的草案中还有许多现在无法实现的特性,比如自定义 overflow 行为,CSS 语法扩展 API,原生滚动行为 API 等等,Houdini 的未来是相当值得期待的。


这些是深入阅读的一些文章,大家有兴趣可以看一下。


谢谢大家!

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注