Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >「译」使用 CSS Parser 解析器扩展来填充 CSS(提案)

「译」使用 CSS Parser 解析器扩展来填充 CSS(提案)

作者头像
泯泷、
修改于 2025-05-09 02:56:39
修改于 2025-05-09 02:56:39
330
举报
文章被收录于专栏:外文翻译外文翻译

链接:https://www.bram.us/2025/05/04/css-parser-extensions-pitch

作者:Bram.us

原标题:Polyfilling CSS with CSS Parser Extensions

四月,我参加了 BlinkOn,这是为 Chromium 开源项目中 Web 平台贡献者举办的会议。在会上,我做了关于“CSS Parser Extensions”的演讲,这是一个我提出的彻底解决 CSS polyfilling 问题的疯狂想法。

如果你不知道,polyfill CSS 特性极其困难,主要是因为 CSS 解析器会丢弃它不理解的内容。那么,如果作者们不必编写自己的解析器和级联来 polyfill CSS 特性,而是可以教会解析器一些新技巧呢?

⚠️ 注意!⚠️ :这是一个个人想法。目前还没有任何官方内容……暂时如此。

我演讲的目标(幻灯片、录音)是吸引在场的一些工程师,并获取他们对这个我过去两年一直在酝酿的疯狂想法的意见。接下来的步骤将是编写一个合适的解释器,然后将其提交给 CSS 工作组以寻求更广泛的兴趣。完成这项工作可能需要数年时间,如果它最终能完成的话。

介绍

在采用新的 CSS 特性时,Web 开发者经常告诉我,他们暂时不会使用某个特性,因为该特性尚未获得跨浏览器支持。在他们的组织内部,通常仍然存在一种期望,即网站在每个浏览器中的外观必须完全一致。或者,他们只是希望只编写一次代码——他们知道这些代码在各种浏览器(包括这些浏览器的一些旧版本)中都能正常运行。

因此,新 CSS 功能的采用——包括那些非常适合渐进增强的功能——被阻止,直到该功能在 Baseline 中广泛可用。假设平均互操作性实现时间为±1.5 年,这意味着一个 CSS 功能在首次在浏览器中发布后,需要 4 年时间才能获得更广泛的采用。

*(当然也有一些例外,还有许多其他因素会影响一个功能的(非)采用,但通常情况下就是这样。)*

典型功能发布的时间线。从功能在第一个浏览器中发布到功能成为 Baseline Widely Available,至少需要 4 年时间。

为了加速新 CSS 特性的采用,可以创建 polyfill。例如,容器查询的 polyfill 已经证明了其价值。然而,这个 polyfill——如同其他任何 CSS polyfill 一样——并非完美,存在一些限制。此外,该 polyfill 约±65%的代码专门用于解析 CSS 并从中提取必要信息,如属性值和容器 at 规则——这显得有些荒谬。

CSS Parser Extensions 旨在通过允许作者扩展 CSS 解析器以支持新语法、属性、关键字等,来消除这些限制并简化信息收集。通过直接接入 CSS 解析器,CSS polyfill 的编写变得更加容易,减少了大小和性能开销,并变得更加健壮。

目前如何(尝试)填充 CSS

*Philip Walton 在演讲《The Dark Side of Polyfilling CSS》中清晰地阐述了这个问题。建议观看此演讲以深入理解该问题。以下是问题陈述的简略版本。*

当开发者为一个 CSS 特性创建 polyfill 时,他们无法依赖 CSS 解析器提供关于他们想要 polyfill 的声明等信息。这是因为 CSS 解析器会丢弃它无法成功解析的规则和声明。因此,polyfill 需要自行收集并重新处理样式表,以获取他们想要 polyfill 的特性的标记。

虽然这看起来像执行这三个步骤一样简单,但实际上比看起来要复杂得多。

  1. 收集所有样式
  2. 解析样式
  3. 应用样式

每个步骤都有其自身的挑战和限制,详情如下,并且 Philip Walton 在 2016 年(!)的这句话很好地总结了这一点:

如果你从未尝试过自己编写 CSS polyfill,那么你可能从未体验过这种痛苦。

– Philip Walton, 《CSS Polyfill 的阴暗面》, 2016 年 12 月

1. 收集所有样式

收集所有样式本身已经具有挑战性,因为作者需要从各种来源中搜集这些样式:

  1. document.styleSheets
  2. document.adoptedStyleSheets
  3. 元素附加样式

在收集了所有这些样式表的引用后,工作就完成了,因为作者还需要留意这些来源中的任何变化。

2. 解析样式

有了所有样式表后,作者可以继续解析样式表的内容。这听起来很简单,但已经带来了挑战,因为在许多情况下,他们无法访问来自 CORS 保护源的样式表内容。

如果他们确实能够访问样式表的内容,作者需要手动对内容进行标记化和解析,重复了用户代理已经完成的工作。

他们对源代码释放的自定义 CSS 解析器也必须与整个 CSS 语法兼容。例如,当用户代理(UA)推出像 CSS 嵌套这样的功能时,polyfill 的 CSS 解析器也需要支持它。因此,用于 CSS polyfill 的 CSS 解析器需要不断追赶以支持最新的语法。

3. 应用样式

在解析样式后,作者必须确定需要将样式应用到哪些元素上。例如,对于声明,这基本上意味着他们需要编写自己的级联规则。他们还需要实现 CSS 功能,如媒体查询,并将其考虑在内。哦,还有 Shadow DOM,这使事情变得更加复杂。

提议的解决方案

如果,与其让 polyfill 作者编写自己的 CSS 解析器和级联,他们可以教解析器一些新技巧呢?

例如:通过 CSS.parser 让作者使用 JavaScript 访问 CSS 解析器——这样他们就可以扩展它,支持新的语法、属性、关键字和函数。

* CSS 关键词: CSS.parser.registerKeyword(…)

* CSS 函数: CSS.parser.registerFunction(…)

* CSS 语法: CSS.parser.registerSyntax(…)

* CSS 声明: CSS.parser.registerProperty(…)

在 CSS 解析器中注册这些功能之一后,解析器将不再丢弃与其相关的令牌,作者可以像解析器从未丢弃它们一样使用该功能。

例如,当注册一个不受支持的 CSS 属性 + 语法时,解析器将保留声明,并且该属性会出现在诸如 window.getComputedStyle() 的内容中。使用 CSS.supports() / @supports() 的功能检查也会通过。

除了这些注册之外,一些实用函数也应提供给作者。例如,获取元素指定样式的方法、将长度计算为其表示的像素值的方法、确定哪些注册已经完成的方法等。

Examples

*⚠️ 这些示例应该能让你了解 CSS Parser Extensions 可以实现的功能。这里的语法并非一成不变,它是我在探索可能性时想出来的。*

注册关键字: random

在以下示例中,不存在的 random 关键字被注册。每当 CSS 引擎解析该关键字时,它将返回一个随机值。

代码语言:js
AI代码解释
复制
CSS.parser

  .registerKeyword('random:<number>', {

    caching\_mode: CSS.parser.caching\_modes.PER\_MATCH,

    invalidation: CSS.parser.invalidation.NONE,

  })

  .computeTo((match) => {

    return Math.random();

  });

;

替换仅在样式表中每次出现时发生一次,这由 caching\_modeinvalidation 选项控制。

注册一个函数: light-dark()

以下代码片段为强大的 light-dark() 提供了兼容性补丁。它是一个根据元素使用的 color-scheme 返回两个传入颜色之一的函数。当 color-schemelight 时,使用第一个值;否则返回第二个值。

代码语言:js
AI代码解释
复制
CSS.parser

  .registerFunction(

    'light-dark(light:<color>, dark:<color>):<color>',

    { invalidation: ['color-scheme'] }

  )

  .computeTo((match, args) => {

    const { element,  property, propertyValue } = match;

    const colorScheme =

      CSS.parser.getSpecifiedStyle(element)

      .getPropertyValue('color-scheme');



    if (colorScheme == 'light') return args.light;

    return args.dark;

  })

);

因为返回值依赖于 color-scheme 值,所以 color-scheme 属性被列为会导致失效的属性。

注册一个函数: at-rule()

以下代码片段为神奇的 at-rule() 函数提供了补丁,该函数允许您检测@规则。它根据检查返回一个 <boolean>

代码语言:js
AI代码解释
复制
CSS.parser

  .registerFunction('at-rule(keyword:<string>):<boolean>', { 

    caching\_mode: CSS.parser.computation\_modes.GLOBAL,

  })

  .computeTo((match, args) => {

    switch (args.keyword) {

      case '@view-transition':

        return ("CSSViewTransitionRule" in window);

      case '@starting-style':

        return ("CSSStartingStyleRule" in window);

      // …

      default:

        return false;

    }

  })

;

由于检测只需执行一次,因此检查结果可以全局缓存。

此处排除了自定义函数。也许应该添加这些函数,也许不应该。

注册属性: size

CSS size 属性是一个全新的属性,最近才被确定。它仍需进行规范制定和实现,并将作为一次性设置 widthheight 的简写方式。

该属性会使用标准特性进行注册。除了用于确定其计算值的 computeTo 方法外, onMatch 方法还会返回一个声明块,用于在检测到使用该属性的声明时进行替换。

代码语言:js
AI代码解释
复制
CSS.parser

  .registerProperty('size', {

      syntax: '[<length-percentage [0,∞]> | auto]{1,2}',

      initialValue: 'auto',

      inherits: false,

      percentages: 'inline-size'

      animatable: CSS.parser.animation\_types.BY\_COMPUTED\_VALUE,

  })

  .computeTo()

  .onMatch((match, computedValue) => {

    const { element, specifiedValue } = match;

    return {

      'width': computedValue[0],

      'height': computedValue[1] ?? computedValue[0],

    };

  });

;
注册属性: scroll-timeline

这是注册属性的另一个示例,即 scroll-timeline 属性。注册和匹配可以分开进行,它还表明可以在匹配时存储一些数据以供后续使用。这里是一个 ResizeObserver ,它被添加到匹配的元素中,并在稍后移除。

代码语言:js
AI代码解释
复制
CSS.parser.registerProperty('scroll-timeline', {});



CSS.parser

  .matchProperty('scroll-timeline')

  // No .computeTo … so it would just return the declared value

  .onMatch(parserMatch => {

    const resizeObserver = new ResizeObserver((entries) => {

        // …

    });

    resizeObserver.observe(parserMatch.element);

    parserMatch.data.set('ro', resizeObserver);

  })

  .onElementUnmatch(parserMatch => {

    const resizeObserver = parserMatch.data.get('ro');

    resizeObserver.disconnect();

  })

;
注册一个语法

也可以注册一个语法以供以后使用。

代码语言:js
AI代码解释
复制
CSS.parser

  .registerSyntax(

    '<single-animation-timeline>',

    'auto | none | <dashed-ident> | <scroll()> | <view()>'

  )

;



CSS.parser

  .registerProperty('animation-timeline', {

    syntax: '<single-animation-timeline>#',

    initialValue: 'auto',

    inherits: false,

    animatable: CSS.parser.ANIMATABLE\_NO,

  })

  .onMatch();
完整示例: position: fixed / visual

在 w3c/csswg-drafts#7475 中,我建议对 position: fixed 进行扩展,允许您指示元素应固定到哪个对象。

  1. position: fixed / layout = 当前行为,将与 position: fixed 相同)
  2. position: fixed / visual = 固定到视觉视口,即使在放大时也是如此
  3. position: fixed / visual-unzoomed = 相对于未缩放的视觉视口定位

用于 polyfill 的代码可能如下所示:

代码语言:js
AI代码解释
复制
// Register syntaxes used by the polyfill.

CSS.parser.registerSyntax('<position>', 'static | relative | absolute | sticky | fixed');

CSS.parser.registerSyntax('<position-arg>', 'layout | visual | visual-unzoomed');



// Extend the existing `position` property registration, only overriding certain parts.

// The non-overriden parts remain untouched

const positionWithArgRegistration = CSS.parser

  .registerProperty('position', {

    extends: 'position',

    syntax: '<position> [/ arg:<position-arg>]?',

  })

  // No .computeTo … so the syntax will compute individually

;



const cssPositionFixed =

    positionWithArgRegistration

      .with('position', 'fixed') // Only `position: fixed`

      .with('arg') // Any arg value

    .onMatch((match) => {

        const { element, specifiedValue } = match;

        const { position, arg } = specifiedValue;



        const styles = CSS.parser.getSpecifiedStyle(element);

        const visualViewport = determineVisualViewport();



        switch (arg) {

            case 'layout':

                return {

                    position: 'fixed',

                };



            case 'visual':                    

                return {

                    position: 'fixed',

                    bottom: (() => {

                        if (styles.bottom.toString() != 'auto') {

                            return styles.bottom.add(CSS.px(visualViewport.height));

                        }

                    })(),

                };



            case 'visual-unzoomed':

                return {

                    position: 'fixed',

                    // @TODO: change all other properties

                };

        }

    })

;



window.visualViewport.addEventListener('resize', () => {

    cssPositionFixed.triggerMatch();

});

总结与注意事项

优势

通过允许 polyfill 作者扩展用户代理自带的 CSS 解析器,他们不再需要收集所有样式、自行解析样式表或确定何时将样式应用于元素。由此产生的 polyfill 将更易于编写、体积更小、性能更快,并且更加健壮和高效。

借助由 CSS 解析器扩展支持的强大 CSS polyfill,CSS 功能的采用不再受限于跨浏览器广泛支持的基线,从而提高了采用率。

此外,这也将使浏览器厂商更容易对功能进行原型设计,因为它需要的前期投入更少。

风险 / 注意事项

为了实现这一点,执行操作的时机至关重要。你不想在像素管道的样式-布局-绘制步骤之间运行阻塞的 JavaScript。这是需要仔细考虑的事情。也许这应该被建模为一个观察者?

目前未包含的是选择器的 polyfill。我还没有对此进行任何思考,因此一旦深入研究后可以添加。我最初的猜测是,像 :has-interest 这样的选择器可以很容易地进行 polyfill,但伪元素的 polyfill 会稍微困难一些,因为你还需要为这些元素修改 DOM。

此外,并非所有的 CSS 特性都可以进行 polyfill。比如视图过渡(View Transitions)这样的功能。

最终,这个想法的成败取决于所有浏览器厂商的支持。如果其中一家(主要)浏览器厂商不支持,那么这个项目将会失败。

那么,接下来呢?

自《可扩展 Web 宣言》 The Extensible Web Manifesto 发布已有 12 年,Philip Walton 分享 CSS polyfill 的难度也有 9 年了,然而自那时以来,情况似乎并没有太大改变。

为了尝试推动这一进展,我的下一步是撰写一份详细的解释文档,并将其提交给 CSS 工作组,以寻求更广泛的关注。我在谷歌的一些同事对此表示感兴趣,并提供了帮助,我也知道 Brian 对此同样感兴趣……所以也许会有更多人(来自其他浏览器厂商)也会感兴趣。

不过,这里要设定一下预期:不要指望这能很快实现。即使最终能够实现,这也需要数年时间,我希望它能成功。

本文系外文翻译,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系外文翻译,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
一步到位:三行CSS代码轻松实现全网站暗黑模式
在另一篇博文中,Mads Stoumann详细介绍了如何使用SVG和CSS重新创建Apple的暗黑模式图标。这证明了他在此领域的深厚技术和创新能力。
前端小智@大迁世界
2023/05/23
1.9K0
一步到位:三行CSS代码轻松实现全网站暗黑模式
CSS Houdini:用浏览器引擎实现高级CSS效果
Houdini被称之为Magic of styling and layout on the web,看起来十分神秘,但实际上,Houdini并非什么神秘组织或者神奇魔法,它是一系列与CSS引擎相关的浏览器API的总称。
2020labs小助手
2022/07/11
8860
CSS 实用新特性:交互、组件、效率的革新
本文将盘点与解析 CSS 的实用新特性,结合技术原理、应用场景和开发实践。从重塑交互体验,到强化组件功能,再到全面优化开发流程,这些新特性不仅显著提升了开发者的工作效率,还为用户带来了更加流畅、互动性更强的网页体验。
球球的前端奶茶屋
2025/04/09
2630
CSS 实用新特性:交互、组件、效率的革新
2020年你不应该错过的CSS新特性
@argyleink在第四次的伦敦(LondonCSS 2020)CSS活动中分享了一个有关于CSS特性相关的话题。看了一下这个主题的PPT,里面有些新东西还是蛮有意思的。基于该PPT,我稍微整理近24个CSS方面的新特性,感兴趣的同学可以继续往下阅读。
童欧巴
2020/11/02
1.2K0
2020年你不应该错过的CSS新特性
大胆尝试这些新的CSS属性,释放CSS的力量吧(一)
本文章系《Unleashing the Power of CSS》(释放CSS的力量,暂且这么翻译吧)一书的学习笔记,希望通本书的学习,系统的梳理下CSS相关的高级新特性。本篇文章是其第一部分,由于全书英文版,理解和阅读会有偏差,欢迎各位大佬们指正,我们一起共同提高。
前端达人
2023/09/11
3611
大胆尝试这些新的CSS属性,释放CSS的力量吧(一)
现代 CSS 指南 -- at-rule 规则扫盲
这里表示的是与屏幕宽度相关的样式设置,上面的代码表示当屏幕宽度大于 900px 时,内部的样式代码块才能生效。
Sb_Coco
2022/11/14
1.2K0
现代 CSS 指南 -- at-rule 规则扫盲
使用 PostCSS 插件让你的网站支持暗黑模式
最近公司需要给多个 webapp(大概20+)加上多皮肤的功能,原先默认是白色皮肤,我们先从暗黑模式入手,从而逐渐实现多皮肤功能。本篇记录下实现思路。
狂奔滴小马
2021/11/15
8870
使用 PostCSS 插件让你的网站支持暗黑模式
H5 项目如何适配暗黑模式
如果系统设置了深色模式,H5页面不做相应的处理,会出现背景色冲突、深色文字显示异常,深色图标显示异常等一些显示上的问题。
@超人
2021/07/05
2.8K0
只听说过CSS in JS,怎么还有JS in CSS?
CSS in JS是一种解决css问题想法的集合,而不是一个指定的库。从CSS in JS的字面意思可以看出,它是将css样式写在JavaScript文件中,而不需要独立出.css、.less之类的文件。将css放在js中使我们更方便的使用js的变量、模块化、tree-shaking。还解决了css中的一些问题,譬如:更方便解决基于状态的样式,更容易追溯依赖关系,生成唯一的选择器来锁定作用域。尽管CSS in JS不是一个很新的技术,但国内的普及程度并不高。由于Vue和Angular都有属于他们自己的一套定义样式的方案,React本身也没有管用户怎样定义组件的样式[1],所以CSS in JS在React社区的热度比较高。
疯狂的技术宅
2021/04/23
7.3K0
只听说过CSS in JS,怎么还有JS in CSS?
网页主题自动适配:网页跟随系统自动切换主题
方式3:使用类名切换,通过对顶层节点设置不同的类名,然后定义不同类名的CSS样式,切换主题时修改类名即可
老K博客
2024/06/20
3310
2024年,你需要了解下这 12 个现代化 CSS 新属性
有时候,提升你的应用程序的CSS只需要一个简单的一行升级或增强!了解这12个属性,开始将它们融入到你的项目中,享受减少技术债务、去除JavaScript,以及为用户体验赢得轻松的胜利。
前端达人
2024/01/31
2K0
2024年,你需要了解下这 12 个现代化 CSS 新属性
实战中学习浏览器工作原理 — HTML 解析与 CSS 计算
上一部分我们完成了从 HTTP 发送 Request,到接收到 Response,并且把 Response 中的文本都解析出来。
三钻
2020/10/29
1.6K0
实战中学习浏览器工作原理 — HTML 解析与 CSS 计算
深色模式适配指南
随着 iOS 13 的发布,深色模式(Dark Mode)越来越多地出现在大众的视野中,支持深色模式已经成为现代移动应用和网站的一个潮流,前段时间更是因为微信的适配再度引起热议。深色模式不仅可以大幅减少电量的消耗,减弱强光对比,还能提供更好的可视性和沉浸感。
政采云前端团队
2020/08/18
3K0
深色模式适配指南
如何在CSS中使用变量
原文链接:https://www.sitepoint.com/how-to-use-variables-in-css/[1]
chuckQu
2022/09/20
3.2K0
如何在CSS中使用变量
让你兴奋不已的13个CSS技巧🤯
快来免费体验ChatGpt plus版本的,我们出的钱 体验地址:https://chat.waixingyun.cn 可以加入网站底部技术群,一起找bug,另外新版作图神器已上线 https://cube.waixingyun.cn/home
前端小智@大迁世界
2023/08/18
4530
让你兴奋不已的13个CSS技巧🤯
仅使用HTML和CSS的亮暗模式按钮切换
我的目标之一是使每个工具都可以不使用javascript,以一定程度上简化代码,同时也是个挑战。
鲸落c
2022/11/14
4.5K0
仅使用HTML和CSS的亮暗模式按钮切换
让我们来构建一个浏览器引擎吧
前端有一个经典的面试题:在浏览器地址栏输入URL到最终呈现出页面,中间发生了什么?
五月君
2021/04/22
1.3K0
JavaScript 实现 JSON 解析器
本周 Cassidoo 每周时事通讯[1]的面试问题是:编写一个函数,该函数接受一个有效的JSON字符串并将其转换为一个对象。编程语言不限,数据结构不限。输入示例:
WecTeam
2019/12/30
3.7K0
DarkMode(2):深色模式解决方案——css颜色变量实现Dark Mode
暗黑模式实现,最初的设计,就是参考之前的主题模式。所谓多套主题/配色/皮肤,就是我们很常见的换肤功能。换肤简单的实现就是更换 css实现不同样式呈现不同肤色。
周陆军
2020/12/11
3.6K0
浏览器解析 CSS 样式的过程
一旦 CSS 被浏览器下载,CSS 解析器就会被打开来处理它遇到的任何CSS。这可以是单个文档内的CSS、<style>标记内的CSS,也可以是 DOM 元素的style属性内嵌的 CSS。所 有CSS 都根据语法规范进行解析和标记。解析完成后,就会生成有一个包含所有选择器、属性和属性各自值的数据结构。
前端小智@大迁世界
2019/04/18
1.8K0
相关推荐
一步到位:三行CSS代码轻松实现全网站暗黑模式
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档