【译文】第四期:CSS选择器性能的真相

74 阅读10分钟

hello,我是海海

这一期是关于CSS选择器性能的真相的一篇译作。阅读时间15分钟

欢迎转载,请注明原文和作者

有任何疑惑的地方,欢迎后台留言

声明:本文观点除了笔者明确添加的表示自己的部分,其他均为原作者观点,与本人无关。

The truth about CSS selector performance

作者:Patrick Brosset

可以在本文中,找到性能更好的选择器,但更重要的是能学到关于css的新知识

文章分类

文章分类


前置知识:

  • CSS 逻辑属性与逻辑值:

本期大纲:

  • 快速一览
  • 在实践中,选择器的性能真的重要吗?
  • 性能测算
  • 嘿嘿,选择器统计工具来啦!
  • 案例分析
  • 结论

CSS 逻辑属性与逻辑值

逻辑属性和逻辑值用抽象术语块向和行向描述其流向。

这些术语的实体含义取决于书写模式。

「块向尺度」

与行内文本流向垂直的方向上的尺度,如横排书写模式中的竖直尺度,以及竖排书写模式中的水平尺度。对于标准的英文文本,此尺度指竖直尺度。

「行向尺度」

与行内文本流向平行的方向上的尺度,如横排书写模式中的水平尺度,以及横排书写模式中的竖直尺度。对于标准的英文文本,此尺度指水平尺度。

参考MDN

「简单来说」

CSS逻辑属性和逻辑值是用来支持国际化布局的方法,因为不同国家和地区的书写顺序不同。

快速一览

编写css选择器的方式确实会影响浏览器呈现网页的方式。

每当页面的一部分发生更改时,运行它的浏览器引擎就需要查看新的DOM树,并根据可用的CSS样式表确定如何设置其样式,这种将样式与DOM节点匹配的操作称为sytyle recalculation。

在不涉及很多细节的情况下,浏览器引擎需要查看所有规则并决定哪些规则适用于给定元素,为此,引擎需要查看选择器,并且从右到左进行。

例如,当引擎看到像".wrapper .section .title .link"这样的选择器时,它会首先尝试将"link"类与该元素进行匹配,如果匹配,则从右到左沿着链想上查找到一个具有"title"类的祖先元素,然后找到一个具有"section"类的祖先元素,最后找到一个具有"wrapper"类的祖先元素。

此实例说明浏览器引擎仅匹配".link"可能比匹配较长的".wrapper .section .title .link"选择器更快,只是需要检查的东西更少。

当然,class选择器并不是唯一选择。一个有趣的例子是使用属性选择器并进行子字符串匹配,如[class*="icon-"]。

这种类型的选择器要求浏览器引擎不仅要检查元素是否具有class属性,还要检查该属性的值是否包含字符串"icon-"。

在实践中,选择器的性能真的重要吗?

「Maybe」.

这在很大程度上取决于网页,DOM树的大小,CSS规则的数量以及DOM是否经常更改。不幸的是,没有标准答案。

事实上,说到标准答案。行业喜欢为好坏制定标准。标准帮助我们快速做出决策,并在编写代码和设计软件时指导我们。但它们也会让我们容易忽略背后的原理。

在编写CSS选择器时,严格应用规则或者使用linter工具自动执行,在某些情况下效果可能适得其反。

过于复杂的CSS选择器,再加上频繁变更的巨大DOM树,很可能会导致性能不佳。但有一个平衡,仅仅为了满足linter并希望获得更好性能,而对理论上的规则进行过度遵循和更改选择器可能只会使你的CSS更难以阅读和维护,而没有多少实际收益。

性能测算

Microsoft Edge DevTools 提供了性能工具,帮助测量程序性能。

由于开发者的设备往往比用户的性能更好,所以需要降低CPU和网络速度来感受用户的体验。

嘿嘿,选择器统计工具来啦!

从Microsoft Edge 109开始,DevTools中的性能工具可以在任何样式重新计算中列出最耗资源的选择器。

具体步骤如下:

  • 打开性能工具Performance
  • 单击右上角的齿轮图标/设置
  • 选中启用Enable advanced rendering instrumentation (slow) 选项
  • 单击Record,在需要测试的网页上执行,然后单击Stop
  • 在记录的配置文件中,找到一个你想要改进的长时间样式重新计算,并在瀑布视图(Main section)中选择它
  • 在底部的标签栏中,点击选择器统计Selector Stats

DevTools 现在为您提供浏览器引擎在此重新计算操作期间计算的所有 CSS 选择器的列表。您可以按选择器处理时间匹配次数对选择器进行排序。

图1-2

图1-2

案例分析

为了让事情变得更实用,让我们尝试改进一个实际的网页。我们将使用为此目的构建的照片库页面作为演示。

图1-3

图1-3

该页面顶部有一个工具栏,可以按相机型号、光圈、曝光时间等过滤照片,并且现在在相机型号之间切换感觉有点慢。

尽管这个演示页面是专门为此构建的,但它确实展示了一个与我们在微软自己的产品中遇到的情况类似的案例。 Edge 团队和 Microsoft 依赖 Web 平台的其他产品团队在这一领域密切合作,以创造最佳的用户体验。在某些特定场景中,我们在具有大量 DOM 元素的应用程序中看到异常长的样式重新计算(例如我们将在此处使用的演示页面,其中包含大约 5000 个元素)。访问 CSS 选择器统计工具对我们帮助很大。

「选择器需要匹配的工作越多,匹配的次数越多,我们通过改进该选择器获得的潜在胜利就越多。」

在上面的列表中,以下选择器看起来很有趣:

.gallery .photo .meta ::selection {}

.gallery .photo .meta li strong:empty {}

[class*=" gallery-icon--"]::before {}

.gallery .photo .meta li {}

* {}

html[dir="rtl"] .gallery .photo .meta li button {}
改进 ::selection 选择器

我们在演示网页中使用“.gallery .photo .meta ::selection”来设置页面照片元数据部分中用户选择的背景和文本颜色的样式。当用户选择照片下方的文本时,将使用自定义颜色而不是浏览器默认颜色。

由于代码中的错误,这种特殊情况实际上是有问题的。选择器实际上应该是“.gallery .photo .meta::selection”,而“.meta”和“::selection”之间没有多余的空格。

因为那里有一个额外的空间,我们的选择器实际上被引擎解释为:“.gallery .photo .meta *::selection”,这使得在样式重新计算期间匹配速度变慢,因为引擎需要检查所有 DOM 元素,然后验证它们是否嵌套在正确的祖先中。

如果没有额外的空间,引擎只需要在进一步检查之前检查元素是否具有“.meta”类。

改进 :empty 选择器

选择器“.gallery.photo .meta li strong:empty”乍一看很可疑。:empty 伪表示选择器仅在 strong 元素没有任何内容时匹配。

这可能需要引擎做更多的工作,而不仅仅是检查元素的标签名称,但这非常有用。

然而,看看与此规则接近的其他 CSS 规则,我们可以看到以下内容:

.gallery .photo .meta li strong:empty {
  padding: .125rem 2rem;
  margin-left: .125rem;
  backgroundvar(--dim-bg-color);
}

html[dir="rtl"] .gallery .photo .meta li strong:empty {
  margin-left: unset;
  margin-right: .125rem;
}

相同的选择器重复两次,但第二个实例以“html[dir=rtl]”为前缀,当页面上的文本方向从右到左时,这对于覆盖第一个规则很有用。在这种情况下,rtl 方向规则会覆盖左边距并用右边距代替。

为了改善这一点,我们可以使用 「CSS 逻辑属性」 。我们可以使用适应任何文本方向的逻辑方向,而不是指定物理边距方向,如下所示:

.gallery .photo .meta li strong:empty {
  padding: .125rem 2rem;
  margin-inline-start: .125rem;
  backgroundvar(--dim-bg-color);
}

改进 [class*=" gallery-icon--"] 选择器

我们的下一个选择器是这个看起来复杂的属性选择器:[class*=" gallery-icon--"]::before

属性选择器非常有用,因此在删除它们之前,请检查它们是否真的会产生负面影响。在我们的例子中,这个选择器似乎确实发挥了作用。

以下是我们使用此选择器的 CSS 规则:

[class*=" gallery-icon--"]::before {
  content'';
  display: block;
  width1rem;
  height1rem;
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
  filtercontrast(0);
}

.gallery-icon--camera::before { background-imageurl(...); }
.gallery-icon--aperture::before { background-imageurl(...); }
.gallery-icon--exposure::before { background-imageurl(...); }
...

以下是改进后的:

.gallery-icon::before {
  content'';
  display: block;
  width1rem;
  height1rem;
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
  filtercontrast(0);
}

.gallery-icon.camera::before { background-imageurl(...); }
.gallery-icon.aperture::before { background-imageurl(...); }
.gallery-icon.exposure::before { background-imageurl(...); }

改进 .gallery .photo .meta li 选择器

这个选择器看起来确实没什么攻击性。但是,如前所述,它仍然强制浏览器去检查“li”元素的祖先列表中的多个级别。知道我们的网页有很多“li”元素,这可能需要大量工作。

我们可以通过为“li”元素指定一个特定的类并删除不必要的嵌套来简化这一过程。例如:

.photo-meta {
  display: flex;
  align-items: center;
  gap: .5rem;
  height1.5rem;
}

改进 * 选择器

“*”符号在 CSS 中用作匹配任何元素的通用选择器。这种匹配任何内容的能力意味着引擎需要将关联规则应用于所有元素。

正如我们在性能记录中看到的,这个选择器确实被匹配了很多次。 CSS 规则的实际作用值得研究一下。

在我们的例子中,它应用了特定的“box-sizing”值:

* {
  box-sizing: border-box;
}

这在 CSS 中很常见,但在我们的例子中,删除它实际上是有意义的,仅在需要的地方应用“box-sizing”,然后查看收益。

查看优化之后的结果

图1-4

图1-4

在上面的性能记录中,相同的重新计算样式块运行时间几乎为一秒,现在运行时间约为 300 毫秒,这真是一个巨大的胜利!

结论

案例研究表明,改进某些 CSS 选择器可以带来重要的性能提升。然而,重要的是要记住,这将取决于您的特定用例。使用性能工具测试网页的性能,如果您发现样式重新计算导致场景变慢,请使用 Microsoft Edge 中的新选择器统计窗格。

与往常一样,如果您对 DevTools 团队有任何反馈,请通过在我们的 GitHub 存储库上打开新问题来与我们联系。


好了,今天的旅程到这里就结束了,谢谢你的陪伴!

感谢你的耐心阅读,如果觉得好的话,可以给我点个赞吗

创作不易,感谢你的支持!

创作不易,感谢你的支持!

本文使用 markdown.com.cn 排版