您是否曾想过 CSS 要完成的工作量?您更改一个属性,整个网站的布局突然就变了。这有点像魔法。到目前为止,我们 Web 开发者社区只能见证和观察这种魔力。如果我们想创造自己的魔法,该怎么办?如果我们想成为魔术师,该怎么做?
欢迎使用 Houdini!
Houdini 工作组由 Mozilla、Apple、Opera、Microsoft、HP、Intel 和 Google 的工程师组成,他们共同致力于向 Web 开发者公开 CSS 引擎的某些部分。该工作组正在处理一系列草稿,目标是让这些草稿获得 W3C 的批准,成为实际的 Web 标准。他们为自己设定了一些高级目标,并将其转换为规范草稿,进而产生了一组支持性的低级规范草稿。
当有人谈论“Houdini”时,通常是指这些草稿的集合。在撰写本文时,草稿列表尚未完成,其中一些草稿只是占位符。
规范
Worklet(规范)
工作流本身并不实用。它们是一种概念,旨在为后续的许多草稿提供可能。如果您在看到“worklet”时想到了 Web Worker,那您没有错。它们在概念上有很多重叠之处。既然我们已经有了 Worker,为什么还要推出这个新功能?
Houdini 的目标是公开新的 API,以便 Web 开发者将自己的代码连接到 CSS 引擎和周围系统。假设其中一些代码段必须在每一帧运行,这可能并不不切实际。其中一些是必须的,引用 Web Worker 规范:
这意味着,Web Worker 不适用于 Houdini 计划执行的任务。因此,我们发明了 Worklet。工作流利用 ES2015 类定义一组方法,这些方法的签名由工作流的类型预定义。它们是轻量级且短暂的。
CSS Paint API(规范)
在 Chrome 65 中,Paint API 默认处于启用状态。阅读详细介绍。
合成器 worklet
此处介绍的 API 已废弃。我们重新设计了 compositor worklet,并将其命名为“Animation Worklet”。详细了解 API 的当前迭代。
尽管 compositor worklet 规范已移至 WICG 并将进行迭代,但它是我最为兴奋的规范。CSS 引擎会将某些操作外包给计算机的显卡,但这通常取决于显卡和设备。
浏览器通常会采用 DOM 树,并根据特定条件决定为某些分支和子树分配自己的层。这些子树会将自己绘制到该 Surface 上(未来可能会使用绘制 Worklet)。最后一步是,将所有这些现已绘制的图层堆叠并彼此叠加,并遵循 z 深度索引、3D 转换等,以生成屏幕上显示的最终图片。此过程称为合成,由合成器执行。
合成过程的优势在于,当网页滚动一点点时,您不必让所有元素重新绘制自己。不过,您可以重复使用上一个帧中的层,只需使用更新后的滚动位置重新运行合成器即可。这样可以加快速度。这有助于我们达到 60fps。
顾名思义,借助混合渲染器 Worklet,您可以钩入混合渲染器,并影响已绘制的元素层在其他层之上的排列方式和层次结构。
更具体地说,您可以告知浏览器您想钩入特定 DOM 节点的绘制流程,并请求访问滚动位置、transform
或 opacity
等特定属性。这会强制将此元素放置在自己的层上,并且在每个帧都会调用您的代码。您可以通过操控图层转换来移动图层,并更改其属性(例如 opacity
),从而以高达 60 fps 的速度执行各种精彩的操作。
以下是使用 compositor 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 为 compositor worklet 编写了 polyfill,您可以试一试,不过显然性能会受到更大影响。
布局 Worklet(规范)
已提出第一个实际规范草稿。实施还需要一段时间。
再次重申,此规范实际上是空的,但这个概念很有趣:编写自己的布局!布局 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(规范)
类型化 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}
属性和值
(规范)
您是否了解 CSS 自定义属性(或其非官方别名“CSS 变量”)? 这是它们,但添加了类型!到目前为止,变量只能具有字符串值,并且使用简单的搜索和替换方法。借助此草稿,您不仅可以为变量指定类型,还可以定义默认值,并使用 JavaScript API 影响继承行为。从技术层面来说,这还允许使用标准 CSS 转换和动画为自定义属性添加动画效果,我们也在考虑这一点。
["--scale-x", "--scale-y"].forEach(function(name) {
document.registerProperty({
name: name,
syntax: "<number>",
inherits: false,
initialValue: "1"
});
});
字体测量
字体测量参数的含义与字面意思完全相同。当我使用字体 Y 以大小 Z 渲染字符串 X 时,边界框(或边界框)是什么?如果我使用 ruby 注解,会怎么样?用户一直在请求此功能,Houdini 应该终于能实现这些愿望了。
但不止如此!
Houdini 的草稿列表中还有更多规范,但这些规范的未来发展前景相当不确定,它们只不过是一些想法的占位符。例如,自定义溢出行为、CSS 语法扩展 API、原生滚动行为的扩展,以及类似的雄心勃勃的功能,这些功能都让 Web 平台上实现了以前无法实现的功能。