您有没有想过 CSS 所做的工作量很大?当您更改单个属性时,整个网站突然以不同的布局显示。就像魔法一样。到目前为止,我们(网络开发者社区)只能见证并观察这种奇妙之处。如果我们想构思出自己的魔法,该怎么办?如果我们想成为魔术师,该怎么办?
胡迪尼上场!
Houdini 任务组由来自 Mozilla、Apple、Opera、Microsoft、HP、Intel 和 Google 的工程师组成,他们齐心协力,向 Web 开发者公开 CSS 引擎的某些部分。该任务组正在筹备草稿集合,目标是让 W3C 接受这些草稿,从而成为实际的 Web 标准。他们为自己设定了一些高级目标,将其转换成了规范草案,进而催生了一系列较低级别的配套规范草案。
当有人谈论“Houdini”时,通常指的是收集到这些草稿。在撰写本文时,草稿列表并不完整,部分草稿只是占位符。
规范
Worklet(spec)
Worklet 本身的用处并不大。我们引入这些概念是为了支持后期的许多草稿。如果您在阅读“worklet”时想到 Web Worker,没有错。它们在概念上有很多重叠。既然已经有 worker,又何必另辟蹊径呢?
Houdini 的目标是公开新的 API,以允许 Web 开发者将自己的代码连接到 CSS 引擎和周围系统。假设某些代码段必须单独运行,可能不切实际。其中一些必须按照定义来回答。引用 Web Worker 规范:
这意味着 Web Worker 无法实现 Houdini 打算执行的操作。因此,我们发明了 Worklet。Worklet 使用 ES2015 类定义方法集合,其签名按 Worklet 类型预定义。它们是轻量级的,而且生命周期很短。
CSS Paint API(spec)
在 Chrome 65 中,Paint API 默认处于启用状态。阅读详细说明。
合成器 Worklet
此处介绍的 API 已过时。合成器 Worklet 经过了重新设计,现已提议为“动画 Worklet”。详细了解 API 的当前迭代。
尽管合成器 Worklet 规范已移至 WICG 并将不断迭代,但它是最让我兴奋的规范。某些操作由 CSS 引擎外包到计算机的显卡,但这通常取决于您的显卡和设备。
浏览器通常会采用 DOM 树,并根据特定条件决定为一些分支和子树提供自己的层。这些子树会自行绘制到其上(将来可能会使用绘制 Worklet)。最后,所有这些层(现已绘制)都会堆叠并相互叠加,并遵循 Z 索引、3D 转换等因素,以生成屏幕上显示的最终图像。此过程称为“合成”,由合成器执行。
合成过程的优势在于,您无需在页面稍微滚动时让所有元素自行重绘。相反,您可以重复使用上一帧中的层,并且只需使用更新后的滚动位置重新运行合成器即可。这样可以提高速度。这有助于我们达到 60fps。
![合成器 Worklet。](https://developer.chrome.google.cn/static/docs/css-ui/houdini/image/compositor-worklet-a5eb4330b62ac.png?authuser=5&hl=sr)
顾名思义,合成器 Worklet 可让您连接到合成器,并影响已绘制的元素层、在其他层之上放置和分层的方式。
为了更具体一点,您可以告知浏览器您希望接入某个 DOM 节点的合成进程,并且可以请求访问某些属性,例如滚动位置、transform
或 opacity
。这会强制将此元素置于其自己的层,并在调用您的代码的每一帧上。您可以通过操纵层转换和更改其属性(如 opacity
)来移动层,从而以高达 60 fps 的速度实现奇特的优化。
以下是使用合成器 Worklet 实现视差滚动的完整实现。
// main.js
window.compositorWorklet.import('worklet.js')
.then(function() {
var animator = new CompositorAnimator('parallax');
animator.postMessage([
new CompositorProxy($('.scroller'), ['scrollTop']),
new CompositorProxy($('.parallax'), ['transform']),
]);
});
// worklet.js
registerCompositorAnimator('parallax', class {
tick(timestamp) {
var t = self.parallax.transform;
t.m42 = -0.1 * self.scroller.scrollTop;
self.parallax.transform = t;
}
onmessage(e) {
self.scroller = e.data[0];
self.parallax = e.data[1];
};
});
Robert Flack 已为合成器 Worklet 编写了一个 polyfill,因此您可以试用一下;这显然对性能有更好的影响。
布局 Worklet(spec)
第一个真正的规范草稿已提出。实施是一个不错的选择。
同样,它的规范实际上为空,但其概念很有趣:编写您自己的布局!布局 Worklet 应该使您能够执行 display: layout('myLayout')
并运行 JavaScript,以便在节点的框中排列节点的子项。
当然,运行 CSS flex-box
布局的完整 JavaScript 实现比运行等效的原生实现要慢,但很容易想象这样一种情形:切入角落可以提升性能。假设某个网站只有图块,例如 Windows 10 或砖石式布局。既不使用绝对定位和固定定位,z-index
也不会使用任何元素,也不会使元素发生重叠或具有任何类型的边框或溢出。能够在重新布局上跳过所有这些检查可以实现性能提升。
registerLayout('random-layout', class {
static get inputProperties() {
return [];
}
static get childrenInputProperties() {
return [];
}
layout(children, constraintSpace, styleMap) {
const width = constraintSpace.width;
const height = constraintSpace.height;
for (let child of children) {
const x = Math.random()*width;
const y = Math.random()*height;
const constraintSubSpace = new ConstraintSpace();
constraintSubSpace.width = width-x;
constraintSubSpace.height = height-y;
const childFragment = child.doLayout(constraintSubSpace);
childFragment.x = x;
childFragment.y = y;
}
return {
minContent: 0,
maxContent: 0,
width: width,
height: height,
fragments: [],
unPositionedChildren: [],
breakToken: null
};
}
});
类型化 CSSOM(spec)
类型化 CSSOM(CSS 对象模型或级联样式表对象模型)解决了我们可能都遇到过并且刚刚学会应付的问题。让我们用一行 JavaScript 来说明一下:
$('#someDiv').style.height = getRandomInt() + 'px';
我们正在进行数学运算,将数字转换为字符串以附加一个单位,这只是为了让浏览器解析该字符串,并将其转换回供 CSS 引擎使用的数字。如果使用 JavaScript 操控转换,情况会更糟。不行了!CSS 即将进行一些输入。
此草稿是较成熟的草稿之一,并且已经在使用 polyfill。(免责声明:使用 polyfill 显然会增加更多计算开销。关键在于展示该 API 有多方便。)
您处理的不是字符串,而是元素的 StylePropertyMap
,其中每个 CSS 属性都有自己的键和对应的值类型。width
等属性使用 LengthValue
作为其值类型。LengthValue
是所有 CSS 单元(如 em
、rem
、px
、percent
等)的字典。设置 height: calc(5px + 5%)
会生成 LengthValue{px: 5, percent: 5}
。某些属性(如 box-sizing
)只接受某些关键字,因此具有 KeywordValue
值类型。这样便可在运行时检查这些属性的有效性。
<div style="width: 200px;" id="div1"></div>
<div style="width: 300px;" id="div2"></div>
<div id="div3"></div>
<div style="margin-left: calc(5em + 50%);" id="div4"></div>
var w1 = $('#div1').styleMap.get('width');
var w2 = $('#div2').styleMap.get('width');
$('#div3').styleMap.set('background-size',
[new SimpleLength(200, 'px'), w1.add(w2)])
$('#div4')).styleMap.get('margin-left')
// => {em: 5, percent: 50}
属性和值
(spec)
您了解 CSS 自定义属性(或其非官方别名“CSS 变量”)吗? 这些只不过是类型而已!到目前为止,变量只能使用字符串值,并使用了简单的“搜索并替换”方法。此草稿不仅可以让您指定变量的类型,还可以定义默认值并使用 JavaScript API 影响继承行为。从技术上讲,这还可以使自定义属性通过标准的 CSS 过渡和动画获得动画效果,这也属于考虑范畴。
["--scale-x", "--scale-y"].forEach(function(name) {
document.registerProperty({
name: name,
syntax: "<number>",
inherits: false,
initialValue: "1"
});
});
字体指标
字体指标正如其名。当我以 Z 字号渲染字体 Y 的字符串 X 时,边界框(或边界框)是什么?如果我使用 ruby 注解,会怎么样?人们呼声很高,Houdini 终于可以实现这些愿望了。
但不止如此!
Houdini 的草稿列表中还有更多规范,但这些规范的未来相当不确定,它们不仅仅是想法的占位符。 例如,自定义溢出行为、CSS 语法扩展 API、原生滚动行为的扩展,以及类似的雄心勃勃的功能,所有这些都可以在网络平台上实现以前无法实现的功能。