Houdini:也许是你从未听说过的最激动人心的 CSS 开发

已发表: 2022-03-10
快速总结 ↬您是否曾经想使用特定的 CSS 功能,但因为并非所有浏览器都完全支持而没有使用? 或者,更糟糕的是,所有浏览器都支持它,但支持有缺陷、不一致甚至完全不兼容? 如果这发生在你身上——我敢打赌它已经发生了——那么你应该关心 Houdini。 Houdini 是一个新的 W3C 工作组,其最终目标是让这个问题永远消失。 它计划通过引入一组新的 API 来做到这一点,这些 API 将首次赋予开发人员扩展 CSS 本身的能力,以及挂钩到浏览器渲染引擎的样式和布局过程的工具。

您是否曾经想使用特定的 CSS 功能,但因为并非所有浏览器都完全支持而没有使用? 或者,更糟糕的是,所有浏览器都支持它,但支持有缺陷、不一致甚至完全不兼容? 如果这发生在你身上——我敢打赌它已经发生了——那么你应该关心 Houdini。

Houdini 是一个新的 W3C 工作组,其最终目标是让这个问题永远消失。 它计划通过引入一组新的 API 来做到这一点,这些 API 将首次赋予开发人员扩展 CSS 本身的能力,以及挂钩到浏览器渲染引擎的样式和布局过程的工具。

关于 SmashingMag 的进一步阅读

  • 为什么应该停止在本地安装 WebDev 环境
  • CSS 的未来:实验性 CSS 属性
  • 53 种你离不开的 CSS 技术

但这具体是什么意思? 这甚至是个好主意吗? 它将如何帮助我们的开发人员现在和将来构建网站?

跳跃后更多! 继续往下看↓

在本文中,我将尝试回答这些问题。 但在我这样做之前,重要的是要弄清楚今天的问题是什么以及为什么需要改变。 然后,我将更具体地讨论 Houdini 将如何解决这些问题,并列出目前正在开发的一些更令人兴奋的功能。 最后,我将提供一些我们作为 Web 开发人员今天可以做的具体事情,以帮助 Houdini 成为现实。

胡迪尼试图解决什么问题?

每当我写一篇文章或构建一个展示一些全新 CSS 功能的演示时,不可避免地会有人在评论或 Twitter 上说,“这太棒了! 太糟糕了,我们将无法再使用它 10 年。”

像这样的评论既烦人又不具建设性,我理解这种情绪。 从历史上看,功能提案需要数年时间才能获得广泛采用。 原因在于,在整个网络历史中,将新功能添加到 CSS 中的唯一方法是通过标准流程。

标准流程
标准过程中的步骤。 (查看大图)

虽然我绝对没有反对标准流程,但不可否认这可能需要很长时间!

例如,flexbox 于 2009 年首次提出,开发者至今仍抱怨由于缺乏浏览器支持而无法使用。 当然,这个问题正在慢慢消失,因为现在几乎所有现代浏览器都会自动更新。 但即使使用现代浏览器,提案与功能的普遍可用性之间总会存在滞后。

有趣的是,并非所有网络领域都是如此。 考虑一下最近在 JavaScript 中的工作情况:

填充工艺
polyfill 过程中的步骤。 (查看大图)

在这种情况下,从产生想法到在生产中使用它之间的时间有时可能只是几天的时间。 我的意思是,我已经在生产环境中使用了async / await功能,而且该功能甚至没有在单个浏览器中实现!

您还可以看到这两个社区的总体情绪存在巨大差异。 在 JavaScript 社区中,您会读到人们抱怨事情进展得太快的文章。 另一方面,在 CSS 中,你会听到人们抱怨学习任何新东西是徒劳的,因为他们需要很长时间才能真正使用它。

那么,为什么我们不写更多的 CSS Polyfills 呢?

乍一看,写更多的 CSS polyfills 似乎是答案。 有了好的 polyfill,CSS 可以像 JavaScript 一样快,对吧?

可悲的是,事情并没有那么简单。 填充 CSS 非常困难,并且在大多数情况下,不可能以不完全破坏性能的方式进行。

JavaScript 是一种动态语言,这意味着您可以使用 JavaScript 来填充 JavaScript。 而且因为它是如此动态,所以它的可扩展性极强。 另一方面,CSS 很少用于填充 CSS。 在某些情况下,您可以在构建步骤中将 CSS 转换为 CSS(PostCSS 会这样做); 但是如果你想填充任何依赖于 DOM 的结构或元素的布局或位置的东西,那么你必须运行你的 polyfill 的逻辑客户端。

不幸的是,浏览器并不容易做到这一点。

下面的图表给出了您的浏览器如何从接收 HTML 文档到在屏幕上显示像素的基本概述。 蓝色的步骤显示了 JavaScript 可以控制结果的地方:

渲染过程
JavaScript 访问浏览器的渲染管道。 (查看大图)

画面相当惨淡。 作为开发人员,您无法控制浏览器如何解析 HTML 和 CSS 并将其转换为 DOM 和 CSS 对象模型 (CSSOM)。 您无法控制级联。 您无法控制浏览器如何选择在 DOM 中布局元素或如何在屏幕上直观地绘制这些元素。 而且您无法控制合成器的功能。

您可以完全访问的进程的唯一部分是 DOM。 CSSOM 有点开放。 然而,引用 Houdini 网站的话,它“未指定,跨浏览器不一致,并且缺少关键功能。”

例如,今天浏览器中的 CSSOM 不会向您显示跨域样式表的规则,它会简单地丢弃任何它不理解的 CSS 规则或声明,这意味着如果您想在浏览器中填充一个特性不支持它,你不能使用CSSOM。 相反,您必须遍历 DOM,找到<style>和/或<link rel=“stylesheet”>标签,自己获取 CSS,解析它,重写它,然后将其添加回 DOM。

当然,更新 DOM 通常意味着浏览器必须重新经历整个级联、布局、绘制和合成步骤。

渲染过程 Polyfill
使用 JavaScript 填充浏览器的渲染管道。 (查看大图)

虽然必须完全重新呈现页面可能看起来不会对性能造成太大影响(尤其是对于某些网站),但请考虑这可能发生的频率。 如果你的 polyfill 的逻辑需要运行以响应诸如滚动事件、窗口大小调整、鼠标移动、键盘事件之类的事情——实际上任何时候都发生了任何变化——那么事情就会变得很明显,有时甚至非常缓慢。

当您意识到当今的大多数 CSS polyfill 都包含自己的 CSS 解析器和自己的级联逻辑时,情况会变得更糟。 而且因为解析和级联实际上是非常复杂的事情,这些 polyfill 通常要么太大,要么太有问题。

更简洁地总结一下我刚才所说的一切:如果你希望浏览器做一些不同于它认为应该做的事情(给定你给它的 CSS),那么你必须想办法通过更新和修改来伪造它自己的 DOM。 您无权访问渲染管道中的其他步骤。

但是我为什么要修改浏览器的内部渲染引擎呢?

对我来说,这绝对是整篇文章中最重要的问题。 所以,如果你到目前为止一直在浏览,请慢慢仔细地阅读这部分!

看完最后一节,我相信你们中的一些人在想,“我不需要这个! 我只是在构建普通的网页。 我并不想侵入浏览器的内部或构建一些超级花哨的、实验性的或前沿的东西。”

如果您这么想,那么我强烈建议您退后一步,真正检查您多年来用于构建网站的技术。 想要访问和挂钩到浏览器的样式过程不仅仅是为了构建花哨的演示——它是为了让开发人员和框架作者能够做两件主要的事情:

  • 标准化跨浏览器差异,
  • 发明或填充新功能,以便人们今天可以使用它们。

如果您曾经使用过诸如 jQuery 之类的 JavaScript 库,那么您已经从这种能力中受益了! 事实上,这是当今几乎所有前端库和框架的主要卖点之一。 GitHub 上五个最流行的 JavaScript 和 DOM 存储库——AngularJS、D3、jQuery、React 和 Ember——都做了很多工作来规范跨浏览器的差异,这样你就不必考虑它了。 每个都公开一个 API,并且它可以正常工作。

现在,想想 CSS 及其所有跨浏览器问题。 即使是流行的 CSS 框架(例如 Bootstrap 和 Foundation)声称跨浏览器兼容性实际上并没有规范跨浏览器错误 - 他们只是避免它们。 CSS 中的跨浏览器错误已不再是过去式。 即使在今天,使用 flexbox 等新的布局模块,我们仍面临许多跨浏览器的不兼容问题。

底线是,想象一下,如果您可以使用任何 CSS 属性并确定它在每个浏览器中都能正常工作,那么您的开发生活会变得多么美好。 想想你在博客文章中读到的或在会议和聚会上听到的所有新特性——比如 CSS 网格、CSS 捕捉点和粘性定位。 想象一下,如果您今天可以使用所有这些功能,并且以与原生 CSS 功能一样高效的方式使用它们。 您需要做的就是从 GitHub 获取代码。

这是胡迪尼的梦想。 这就是工作组正在努力实现的未来。

因此,即使您不打算编写 CSS polyfill 或开发实验性功能,您也可能希望其他人能够这样做——因为一旦这些 polyfill 存在,每个人都会从中受益。

目前正在开发哪些 Houdini 功能?

我在上面提到,开发人员对浏览器渲染管道的访问点非常少。 实际上,唯一的地方是 DOM,在某种程度上,还有 CSSOM。

为了解决这个问题,Houdini 工作组引入了几个新规范,这些规范将首次允许开发人员访问渲染管道的其他部分。 下图显示了流水线​​以及可以使用哪些新规范来修改哪些步骤。 (请注意,灰色的规格已计划但尚未编写。)

规格覆盖
新的 Houdini 规范适合浏览器的渲染管道。 (查看大图)

接下来的几节简要概述了每个新规范及其提供的功能类型。 我还应该注意,本文没有提到其他规范; 如需完整列表,请参阅 Houdini 草稿的 GitHub 存储库。

CSS 解析器 API

CSS Parser API 目前未编写; 所以,我所说的大部分内容很容易改变,但基本思想是它使开发人员能够扩展 CSS 解析器并告诉它新的结构——例如,新的媒体规则、新的伪类、嵌套、@ @extends@apply

一旦解析器知道了这些新结构,它就可以将它们放在 CSSOM 中的正确位置,而不仅仅是丢弃它们。

CSS 属性和值 API

CSS 已经有自定义属性,而且,正如我之前所表达的,我对它们解锁的可能性感到非常兴奋。 CSS 属性和值 API 使自定义属性更进一步,并通过添加类型使它们更加有用。

将类型添加到自定义属性有很多很棒的事情,但也许最大的卖点是类型将允许开发人员转换和动画自定义属性,这是我们今天无法做到的。

考虑这个例子:

 body { --primary-theme-color: tomato; transition: --primary-theme-color 1s ease-in-out; } body.night-theme { --primary-theme-color: darkred; }

在上面的代码中,如果将night-theme类添加到<body>元素中,那么页面上每个引用–primary-theme-color属性值的元素都会慢慢地从tomato过渡到darkred 。 如果您今天想这样做,则必须手动为这些元素中的每一个编写转换,因为您无法转换属性本身。

这个 API 的另一个有希望的特性是能够注册一个“应用挂钩”,它为开发人员提供了一种在级联步骤完成后修改元素上自定义属性的最终值的方法,这对于 polyfill 来说可能是一个非常有用的特性。

CSS 类型的 OM

CSS Typed OM 可以被认为是当前 CSSOM 的第 2 版。 它的目标是解决当前模型的许多问题,并包含新的 CSS Parsing API 和 CSS Properties and Values API 添加的功能。

Typed OM 的另一个主要目标是提高性能。 将当前 CSSOM 的字符串值转换为有意义类型的 JavaScript 表示将产生显着的性能提升。

CSS 布局 API

CSS Layout API 使开发人员能够编写自己的布局模块。 我所说的“布局模块”是指可以传递给 CSS display属性的任何东西。 这将首次为开发人员提供一种与原生布局模块(例如display: flexdisplay: table )一样高效的布局方式。

作为一个示例用例,Masonry 布局库显示了开发人员今天愿意在多大程度上实现仅使用 CSS 无法实现的复杂布局。 虽然这些布局令人印象深刻,但不幸的是,它们存在性能问题,尤其是在功能较弱的设备上。

CSS Layout API 通过为开发人员提供一个registerLayout方法来工作,该方法接受一个布局名称(稍后在 CSS 中使用)和一个包含所有布局逻辑的 JavaScript 类。 这是一个基本示例,说明如何通过registerLayout定义masonry

 registerLayout('masonry', class { static get inputProperties() { return ['width', 'height'] } static get childrenInputProperties() { return ['x', 'y', 'position'] } layout(children, constraintSpace, styleMap, breakToken) { // Layout logic goes here. } }

如果上述示例中的任何内容对您来说都没有意义,请不要担心。 主要关心的是下一个示例中的代码。 下载masonry.js文件并将其添加到您的网站后,您可以像这样编写 CSS,一切都会正常工作:

 body { display: layout('masonry'); }

CSS 绘制 API

CSS Paint API 与上面的 Layout API 非常相似。 它提供了一个registerPaint方法,其操作与registerLayout方法一样。 然后,开发人员可以在任何需要 CSS 图像的地方使用 CSS 中的paint()函数,并传入注册的名称。

这是一个绘制彩色圆圈的简单示例:

 registerPaint('circle', class { static get inputProperties() { return ['--circle-color']; } paint(ctx, geom, properties) { // Change the fill color. const color = properties.get('--circle-color'); ctx.fillStyle = color; // Determine the center point and radius. const x = geom.width / 2; const y = geom.height / 2; const radius = Math.min(x, y); // Draw the circle \o/ ctx.beginPath(); ctx.arc(x, y, radius, 0, 2 * Math.PI, false); ctx.fill(); } });

它可以像这样在 CSS 中使用:

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

现在, .bubble元素将以蓝色圆圈作为背景显示。 圆圈将居中并且与元素本身的大小相同,无论发生什么。

工作集

上面列出的许多规范都显示了代码示例(例如registerLayoutregisterPaint )。 如果您想知道将代码放在哪里,答案就在工作集脚本中。

Worklet 类似于 Web Worker,它们允许您导入脚本文件并运行 JavaScript 代码,这些代码 (1) 可以在渲染管道中的各个点调用,并且 (2) 独立于主线程。

Worklet 脚本将严格限制您可以执行的操作类型,这是确保高性能的关键。

复合滚动和动画

虽然目前还没有关于复合滚动和动画的官方规范,但它实际上是更知名和备受期待的 Houdini 功能之一。 最终的 API 将允许开发人员在主线程之外的合成器工作集中运行逻辑,并支持修改 DOM 元素属性的有限子集。 此子集将仅包括可以读取或设置的属性,而无需强制渲染引擎重新计算布局或样式(例如,变换、不透明度、滚动偏移)。

这将使开发人员能够创建高性能的基于滚动和输入的动画,例如粘性滚动标题和视差效果。 您可以在 GitHub 上阅读有关这些 API 试图解决的用例的更多信息。

虽然还没有官方规范,但 Chrome 已经开始了实验性开发。 事实上,Chrome 团队目前正在使用这些 API 最终会公开的原语来实现 CSS 捕捉点和粘性定位。 这太神奇了,因为这意味着 Houdini API 的性能足以让新的 Chrome 功能在它们之上构建。 如果您仍然担心 Houdini 不会像本地人一样快,那么仅凭这一事实就可以说服您。

为了看一个真实的例子,Surma 录制了一个在 Chrome 内部版本上运行的视频演示。 该演示模仿了 Twitter 本地移动应用程序中滚动标题的行为。 要了解它是如何工作的,请查看源代码。

你现在可以做什么?

如前所述,我认为每个建立网站的人都应该关心 Houdini; 这将使我们未来的生活变得更加轻松。 即使您从不直接使用 Houdini 规范,您几乎肯定会使用构建在其之上的东西。

虽然这个未来可能不会立竿见影,但它可能比我们许多人想象的更接近。 所有主要浏览器供应商的代表都参加了今年早些时候在悉尼举行的最后一次 Houdini 面对面会议,对于构建什么或如何进行几乎没有分歧。

据我所知,这不是胡迪尼是否会成为一件事的问题,而是什么时候,这就是你们所有人进来的地方。

浏览器供应商,就像其他构建软件的人一样,必须优先考虑新功能。 这种优先级通常取决于用户对这些功能的渴望程度。

因此,如果您关心 Web 上样式和布局的可扩展性,并且如果您希望生活在一个可以使用新 CSS 功能而无需等待它们通过标准流程的世界中,请与您使用的浏览器的开发人员关系团队,并告诉他们您想要这个。

您可以提供帮助的另一种方式是提供现实世界的用例——您希望能够通过样式和布局来完成目前很难或不可能完成的事情。 GitHub 上的一些草稿都有用例文档,您可以提交拉取请求来贡献您的想法。 如果文档不存在,您可以启动一个。

Houdini 工作组(以及整个 W3C)的成员确实希望 Web 开发人员提供周到的意见。 大多数参与规范编写过程的人都是从事浏览器工作的工程师。 他们本身通常不是专业的 Web 开发人员,这意味着他们并不总是知道痛点在哪里。

他们依靠我们来告诉他们。

资源和链接

  • CSS-TAG Houdini Editor Drafts, W3C 所有Houdini 草稿的最新公开版本
  • CSS-TAG Houdini Task Force Specifications, GitHub 规范更新和开发的官方 Github 存储库
  • Houdini 示例,GitHub 代码示例展示和试验可能的 API
  • Houdini 邮件列表,W3C 提问一般问题的地方

特别感谢 Houdini 成员 Ian Kilpatrick 和 Shane Stephens 审阅本文。