Houdini - 揭秘 CSS

您有没有想过 CSS 的工作量?您更改一个属性,整个网站就会突然以不同的布局显示。这就像魔法。到目前为止,我们这个网络开发者社区 只能见证和观察到我们的魔力如果我们想要构思出自己的魔法效果,该怎么办?如果我们想成为魔术师,该怎么办?

来胡迪尼吧!

Houdini 任务组由 Mozilla、Apple、Opera、Microsoft、HP、Intel 和 Google 的工程师组成,他们携手合作,向 Web 开发者公开 CSS 引擎的某些部分。任务组正在开发草稿集合,目标是让 W3C 接受这些草稿,成为实际的 Web 标准。他们为自己设定了几个高级目标,根据这些目标拟定了规范草稿,进而催生了一系列支持性、较低级别的规范草稿。

有人在谈论“胡迪尼”时,通常所说的是这些草稿。在撰写本文时,草稿列表尚不完整,一些草稿只是占位符。

规范

Worklet(spec

Worklet 本身没有什么实际用处。我们引入这个概念是为了让之后的许多草稿成为可能。如果您在阅读“worklet”时想到 Web Worker,那也没有错。它们在概念上有很多重叠。既然已有 worker,为什么还要出现新事物呢?

Houdini 的目标是公开新的 API,让 Web 开发者能够将自己的代码连接到 CSS 引擎和周围系统。假设其中一些代码段必须运行“every. single. frame”,这种做法可能不太现实。其中有些必须按照定义来引用 Web Worker 规范

这意味着,Web Worker 并不具备 Houdini 的计划可行性。因此,Worklet 诞生了。Worklet 使用 ES2015 类定义方法集合,其签名按 Worklet 的类型预定义。它们是轻量级的短期的。

CSS Paint API(spec

Chrome 65 中默认启用 Paint API。阅读详细说明

合成器 Worklet

此处介绍的 API 已过时。合成器 worklet 已经过重新设计,现在被提议为“Animation Worklet”。详细了解 API 的当前迭代版本

虽然合成器 Worklet 规范已移至 WICG 并将进行迭代,但这是最令我兴奋的规范。某些操作由 CSS 引擎外包到计算机的显卡,但这通常取决于您的显卡和设备。

浏览器通常会获取 DOM 树,并根据特定条件决定为一些分支和子树分配自己的层。这些子树会在其上绘制自身(将来可能会使用绘制 Worklet)。在最后一步中,所有这些单独的图层(现在已绘制)会堆叠并相互堆叠,并遵循 Z 索引、3D 转换等,以生成屏幕上显示的最终图像。此过程称为“合成”,由合成器执行。

合成过程的优势在于,当页面稍微滚动时,您无需让所有元素自行重绘。您可以改为重复使用上一帧中的图层,只需使用更新后的滚动位置重新运行合成器即可。这样可以提高处理速度。这有助于我们达到 60fps。

合成器 Worklet。

顾名思义,通过合成器 Worklet,您可以连接到合成器,并影响已绘制的元素层在其他层之上的位置和分层方式。

为了更加具体,您可以告知浏览器您想要接入某个 DOM 节点的合成进程,并可以请求访问某些属性,例如滚动位置、transformopacity。这会将该元素强制应用到其自己的层,并在每个帧上调用您的代码。您可以通过操控层转换并更改其属性(例如 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 单元(例如 emrempxpercent 等)的字典。设置 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、对原生滚动行为的扩展,以及类似的雄心勃勃的功能,所有这些都在 Web 平台上实现前所未有的功能。

样本歌曲

我已将演示代码(使用 polyfill 的实时演示)开源。