前端开发如何更好的避免样式冲突?级联层(CSS@layer)
作者:vivo 互联网前端团队 - Zhang Jiqi
本文主要讲述了CSS中的级联层(CSS@layer),讨论了级联以及级联层的创建、嵌套、排序和浏览器支持情况。级联层可以用于避免样式冲突,提高代码可读性和可维护性。
一、什么是级联层(Cascade Layers)?
1.1 级联层的官方定义
我们参看Cascading and Inheritance Level 5(13 January 2022) 中6.4节所述:
级联层提供了一种结构化的方式来组织和平衡单个起源中的问题。单个级联层内的规则级联在一起,不与层外的样式规则交错。
开发者可以创建层来表现元素默认值、第三方库、主题、组件、覆盖和其他样式问题,并且能够以显式方式重新排序层级,而无需更改每个层内的选择器或特异性,或依赖源顺序来解决跨层的冲突。
单纯看官方定义和概括可能比较晦涩,下面我们会结合案例来说清楚。
1.2 级联层为了解决什么问题?
简而言之:级联层的出现就是为了使 CSS 开发者可以更简单直接地控制级联。
我们来假设日常开发中的一个场景,从场景去理解级联层在解决什么问题。
如下图:

我们原来的'display'文案是红色,当我们引入了一个第三方组件库,第三方库中有以下样式。
/* 开发者样式 */
.item {
color: red;
}
/* 第三方库 */
#app .item {
color: green;
border: 5px solid green;
font-size: 1.3em;
padding: 0.5em;
width: 120px;
}
就会导致'display'文案变成绿色。
我们想要将'display'文案的颜色由绿色改成红色一般的手段是增加选择器特异性(优先级)。比如在开发页面中对开发者样式进行修改:
/* 开发者样式 */
#app div.item {
color: red;
}
/* 第三方库 */
#app .item {
color: green;
border: 5px solid green;
font-size: 1.3em;
padding: 0.5em;
width: 120px;
}
或者借助级联中出场顺序对优先级的影响在用户页面中重写
/* 第三方库 */
#app .item {
color: green;
border: 5px solid green;
font-size: 1.3em;
padding: 0.5em;
width: 120px;
}
/* 开发者样式 */
#app .item {
color: red;
}
效果如下:

再举个例子:
比如有可能第三方组件写了
a { color: blue; }
那项目开发中由于引入这个第三方组件 就会导致样式污染,因为第三方库的样式往往都在项目设置的通用样式比如common.css后加载。
如果后面想在代码中覆盖某些属性,使用高特异性选择器的语句就可能会导致问题。然后因为有问题就会选择更高特异性的择器的语句或使用!important,这会使代码变得冗长也可能会带来副作用。低特异性选择器的语句很容易被后面出现在代码中的语句覆盖。在自己的代码之后加载第三方 CSS 时特别会出现这种问题。
所以级联层就是为了解决以上场景出现的,级联层在级联中的的位置是在内联样式和选择器特异性之间。当有些css声明就是设置要低优先级且不受选择器特异性影响那么使用级联层再合适不过。
运用级联层解决第一个日常开发场景痛点的css代码如下:
/* 排序层 */
@layer reset, lib;
/* 通用样式 */
@layer reset {
#app .item {
color: black;
width: 100px;
padding: 1em;
}
}
/* 第三方库样式 */
/*我们将第三方库的样式全部放到lib层*/
/*这里一般使用@import导入的方式*/
/*为了示例简单我们简化了操作*/
@layer lib {
#app .item {
color: green;
border: 5px solid green;
font-size: 1.3em;
width: 130px;
}
}
/* 开发者样式 */
.item {
color: red;
}
为了知道为什么上面的css代码能解决冲突问题,更好地理解级联层的作用,理解一些现象背后的根因,了解级联层和级联的关系,我们继续往下看。
二、理解级联层的前提——级联(cascade)
2.1 什么是级联?
CSS中有两个重要的基础规则,一个是继承,一个是级联。
指的是类似 color,font-family,font-size,line-height 等属性父元素设置后,子元素会继承的特性。
可以简单理解为是CSS 用来解决要应用于元素的具体样式的算法。也就是基于一些优先级排序输出给给定元素上属性值一个级联值。级联值是级联的结果。
2.2 当前级联的排序标准
我们参看Cascading and Inheritance Level 5(13 January 2022) 中6.1节,
相比于Cascading and Inheritance Level 4(14 January 2016) 中的定义有明显变化。
最重要的变化就是增加了级联层。由此级联排序变成:
起源和重要性(Origin and Importance)
上下文(Context)
样式属性(Element-Attached Styles)
层(Layers)
特异性(Specificity)
出场顺序(又名源代码顺序)(Order of Appearance)
浏览器在确定最终元素样式呈现的时候,会依据这些准则按照优先权从高到低排序,并且会一个一个的检查,直到确定最终样式。
2.3 级联起源(Cascading Origins)
2.3.1 三个核心起源
css中每个样式规则有三个核心起源,它决定了它进入级联的位置,理解起源优先级是理解级联的关键。
用户代理来源(浏览器内置样式)
用户来源(浏览器的用户设置 )
作者来源(Web开发者)
2.3.2 起源的优先级
Css声明的起源取决于它来自哪里,重要性在于它是否用!important声明。
各种起源的优先级按降序排列:
过渡
重要的用户代理
重要用户
重要作者
动画
普通作者
普通用户
普通用户代理
越靠前来源的声明优先级越高。过渡和动画我们可以暂时忽略。
2.4 熟悉又陌生的 !important
通常作为开发者,!important会被我们视为一种增加特异性的方法,用以覆盖内联样式或特异性较高的选择器。
但是!important设计出来的初衷是作为整体级联中的一个特性,来平衡开发者、用户设置和浏览器内置之间对css优先级的影响能力。
默认情况下三者的优先级是:作者来源> 用户来源>用户代理来源(可以参看上文起源优先级中6-8的排序)。但是当css声明添加!important之后会使它们的优先顺序颠倒(参看上文起源优先级中2-4的排序)。
如果站在浏览器和用户的角度看!important提供了在必要时重获优先级控制权的能力,而非只是简单的增加特异性。
举个例子:
浏览器默认样式表包含我们开发者无法覆盖的重要样式。
浏览器对具有'hidden'类型的input输入框设置了默认的展示属性并且将其声明为重要。
input[type=hidden i] { display: none !important; }
看下面两张图例:
第一张可以看出我们对具有'hidden'类型的input输入框的展示属性设置成了显示并且声明为重要

第二张是样式最终计算结果:隐藏

从上面的浏览器表现可以看到我们作为开发者在作者样式表中设置的规则没能覆盖用户代理样式表中的相同规则。
这验证了上面说的:在级联中!important会颠倒三大核心起源默认优先顺序。
验证的过程中还发现了一个chrome控制台这边的bug,看上面的第一张图例:没生效的规则不划删除线,生效的反而划删除线了。
再看一个官方文档的例子加强一下理解:

font-size的值最终是‘12pt ’。
因为作者样式表中添加!important的规则优先权高于用户样式表中普通规则。
text-indent的值最终是‘1em’。
因为虽然两个样式表都标注了!important,但是标注!important用户声明优先级大于标注!important作者声明。
2.5 一张图带你理解级联
下图可以帮助我们直观的理解级联以及级联层在级联中的位置:

图片来源:css-tricks
我们会发现平时操作最多的选择器特异性(selector specificity)只是级联中的一小部分。也轻松地理解了为什么内联样式优先级天然高。同时我们会发现!important在级联中有特殊地位。他穿插在级联规则的各个阶段并能颠倒优先级。
三、级联层(CSS@layer) 使用探索
3.1 @layer 是什么?
我们来看MDN上的定义:
The @layer CSS at-rule is used to declare a cascade layer and can also be used to define the order of precedence in case of multiple cascade layers.
也就是说 @layer 这个at-rule(AT规则) 用于声明级联层(cascade layer),也能用于定义多个级联层的优先级。
At-rules 是什么?
At-rules是指导 CSS 如何表现的CSS 语句。它们以 at 符号 ' @' ( U+0040 COMMERCIAL AT) 开头,后跟一个标识符,包括下一个分号 ' ;' ( U+003B SEMICOLON) 或下一个CSS 块之前的所有内容。
我们开发常见的at-rule有@charset、@media、@font-face、@keyframes 等。
3.2 @layer的句法规则
@layer的句法如下:
@layer layer-name {rules}
@layer layer-name;
@layer layer-name, layer-name, layer-name;
@layer {rules}
3.3 如何创建级联层
可以通过多种方式创建级联层。
第一种方法是:创建命名的级联层,其中包含该层的 CSS 规则,如下所示:
@layer green {
.item {
color: green;
border: 5px solid green;
font-size: 1.3em;
padding: 0.5em;
width: 120px;
}
}
@layer special {
.item {
color: rebeccapurple;
}
}
第二种方法是:创建一个命名的级联层而不分配任何样式。这可以是单层,如下所示:
@layer green;
可以一次定义多个层,如下:
@layer green, special
一次定义多个层有什么好处呢?
因为声明层的初始顺序决定了层的优先级。与声明一样,如果在多个层中找到声明,最后定义的层声明将最终生效。看下面代码:
@layer green,special;
@layer green {
#app .item {
color: green;
border: 5px solid green;
font-size: 1.3em;
padding: 0.5em;
width: 120px;
}
}
@layer special {
.item {
color: rebeccapurple;
}
}
效果如下图:

special层中item样式规则将被应用即使它的特异性低于 green层中的规则。这是因为一旦层顺序定义完成,就会忽略选择器特异性。
同样也会忽略出现的顺序:
我们声明层名称并设置它们的顺序后,可以通过重新声明名称来将 CSS 规则添加到图层。然后将样式附加到层,并且层顺序不会更改。比如如下代码虽然@layer green重新声明了并在文件后方但是由于顺序一开始已经设置所以字体颜色还是紫色:
@layer green,special;
@layer special {
.item {
color: rebeccapurple;
}
}
@layer green {
.item {
color: green;
border: 5px solid green;
font-size: 1.3em;
padding: 0.5em;
width: 120px;
}
}
效果如下:

忽略选择器特异性和代码出现顺序可以让我们创建更简单的 CSS 选择器,并使代码优雅,因为不必确保选择器具有足够高的特异性来覆盖其他css规则,只需要确保它出现在后面的层中。
第三种方法是:创建一个没有名称的级联层。例如:
@layer {
.item {
color: black;
}
}
这将创建一个匿名级联层,该层功能与命名层相同。但是使用匿名层有如下缺点:
可读性较差:匿名层没有名称,会导致样式表不易阅读和理解。特别是在大型项目中,可能会出现样式不断增加,难以跟踪和管理的问题。
难以扩展:如果稍后想要更改特定样式或组合,也会很难找到特定的代码块。
不可复用性:匿名层中的样式不能在其他地方重复使用或引用。这样会使样式表更难以管理和维护。
平时我们尽量避免使用匿名层。但当我们是样式库的作者,并想将某些css代码不被使用者修改可以借助匿名层做到这一点。
第四种方法是:使用@import。CSS 原生支持 @import 导入其他 CSS 文件。
@import url(index.css) layer(index);
同时,也支持匿名引入,例如:
@import url(index.css) layer;
我们在使用@import时候需要放在除@charset之外的样式规则前,否则无法导入。
可能的第五种方式仍在讨论中:通过元素上的属性。请参阅[css-cascade] Provide an attribute for assigning ato a cascade layer #5853。
3.4 层的嵌套规则
图层可以嵌套。例如:
@layer base {
p { max-width: 70ch; }
}
@layer framework {
@layer base {
p { margin-block: 0.75em; }
}
@layer theme {
p { color: #222; }
}
}
生成的层可以表示为一棵树:
1.base
framework
base
2.theme
或可以用扁平列表表示
base
framework.base
framework.theme
要将样式附加到嵌套层,您需要使用以下全名来引用它:
@layer framework {
@layer default {
p { margin-block: 0.75em; }
}
@layer theme {
p { color: #222; }
}
}
@layer framework.theme {
/* 这些样式会被添加到framework层里面的theme层 */
blockquote { color: rebeccapurple; }
}
看效果:

3.5 层的排序规则
级联层按照它们声明的顺序排序。第一层优先级最低,最后一层优先级最高。但是,未分层的样式具有最高优先级:
/* 未分层 */a { color: green; }
@layer layer-1 { a { color: red; } }
@layer layer-2 { a { color: orange; } }
@layer layer-3 { a { color: yellow; } }
优先级顺序如下:
未分层样式
layer-3
layer-2
layer-1
看下图示例:

层可以在一个地方被定义图层(以建立图层顺序),然后在任何地方添加样式
/* 定义在一个地方 */
@layer my-layer;
/* 其他样式*/
...
/* 在某个地方添加样式 */
@layer my-layer { a { color: red; } }
3.6 加上!important之后的排序规则
/* 未分层 */ a { color: green !important; }
@layer layer-1 { a { color: red !important; } }
@layer layer-2 { a { color: orange !important; } }
@layer layer-3 { a { color: yellow !important; } }
任何加上重要声明的样式都以相反的顺序应用
优先级顺序如下:
!important layer-1
!important layer-2
!important layer-3
!important 未分层样式
看下图示例:

这里我们看到对应规则在chrome浏览器的显示是正确的。但是在开发者控制台中的样式一栏规则显示有问题。应该是chrome浏览器开发者控制台的bug。
3.7 嵌套层的排序规则
@layer layer-1 { a { color: red; } }
@layer layer-2 { a { color: orange; } }
@layer layer-3 {
@layer sub-layer-1 { a { color: yellow; } }
@layer sub-layer-2 { a { color: green; } }
/* 未嵌套 */ a { color: blue; }
}
/* 未分层 样式 */ a { color: black; }
优先级顺序如下:
未分层 样式
layer-3
-layer-3 未嵌套
-layer-3 sub-layer-2
-layer-3 sub-layer-1
layer-2
layer-1

3.8 媒体查询对层排序的影响
以下层顺序将取决于匹配的媒体条件:
例如:
@media (min-width: 600px) {
@layer layout {
.item {
font-size: x-large;
}
}
}
@media (prefers-color-scheme: dark) {
@layer theme {
.item {
color: red;
}
}
}
如果两个媒体查询的规则中匹配一个那么对应的级联层生效。如果两者都匹配,那么图层顺序将为layout, theme都生效。如果两个都不匹配则不定义层。下图是两者都匹配的场景。

四、现在就能使用级联层吗?
4.1 目前浏览器支持程度

目前所有现代浏览器均已经支持 @layer 规则。所有浏览器厂商都支持的特性未来一定比较实用。
4.2 W3C 鼓励可以作为日常使用
SS 的标准化流程由 W3C Cascading Style Sheets Working Group (CSSWG)——W3C层叠样式列表小组以及独立CSS专家组成。W3C 本身并不制定标准,而是作为一个论坛式的平台,接收来自小组成员的提交,并通过会议来商讨制定标准,所有的提交以及讨论都是公开透明的,可以在 W3C 网站上看到会议的记录,可以简单分为4个大阶段:
工作草案( WD )
候选人推荐( CR )
提议的建议( PR )
W3C 推荐( REC )
下图可以帮助理解:

图片来源:w3.org
W3C 通过状态码表示规范的成熟度。成熟度从低到高排序如下图。

图片来源:w3.org
再看下图:包含layer概念的标准讨论已经到达CR阶段。

图片来源:w3.org
W3C 鼓励从 CR阶段的标准 开始可以作为日常使用。
五、总结
最后,我们回到通过级联层如何解决“引入了一个第三方组件库导致样式覆盖“的问题上。
css代码如下:
/* 排序层 */
@layer reset, lib;
/* 通用样式 */
@layer reset {
#app .item {
color: black;
width: 100px;
padding: 1em;
}
}
/* 第三方库样式 */
/*我们将第三方库的样式全部放到lib层,这里一般使用@import导入的方式,为了示例简单我们简化了操作*/
@layer lib {
#app .item {
color: green;
border: 5px solid green;
font-size: 1.3em;
width: 130px;
}
}
/* 开发者样式 */
.item {
color: red;
}
我们将第三方库的样式全部放到lib层,将需要重置的一些样式放到reset层,自己开发的样式不放入层中(当然你也可以放入到一层然后排序在最后)。由此我们实现了样式的分层解决了第三方组件库导致样式覆盖的问题,而且做到开发者样式简单不冗长。
效果如下:

级联层(CSS@layer)已经历概念提出到到浏览器全面支持的阶段。也许在不久的将来大家都会普遍使用它,期望本文能给大家带来一定帮助。
参考资料:
前端开发如何更好的避免样式冲突?级联层(CSS@layer)的更多相关文章
- Web前端开发最佳实践(8):还没有给CSS样式排序?其实你可以更专业一些
前言 CSS样式排序是指按照一定的规则排列CSS样式属性的定义,排序并不会影响CSS样式的功能和性能,只是让代码看起来更加整洁.CSS代码的逻辑性并不强,一般的开发者写CSS样式也很随意,所以如果不借 ...
- 10款让WEB前端开发人员更轻松的实用工具
这篇文章介绍10款让Web前端开发人员生活更轻松的实用工具.每个Web开发人员都有自己的工具箱,这样工作中碰到的每个问题都有一个好的解决方案供选择. 对于每一项工作,开发人员需要特定的辅助工具,所以如 ...
- 十款让 Web 前端开发人员更轻松的实用工具
这篇文章介绍十款让 Web 前端开发人员生活更轻松的实用工具.每个 Web 开发人员都有自己的工具箱,这样工作中碰到的每个问题都有一个好的解决方案供选择. 对于每一项工作,开发人员需要特定的辅助工具, ...
- 【转】十款让 Web 前端开发人员更轻松的实用工具
这篇文章介绍十款让 Web 前端开发人员生活更轻松的实用工具.每个 Web 开发人员都有自己的工具箱,这样工作中碰到的每个问题都有一个好的解决方案供选择. 对于每一项工作,开发人员需要特定的辅助工具, ...
- Web前端开发实战1:二级下拉式菜单之CSS实现
二级下拉式菜单在各大学校站点.电商类站点.新闻类站点等大型?站点非经常见,那么它的实现原理是什么呢? 学习了Web前端开发的知识后,我们是能够实现这种功能的.复杂的都是从基础效果上加入做出来的.原理和 ...
- 前端开发流程之(线上)绝对地址(图片+css+js)
重要提醒:前端写完-----发邮件通知项目组 1:写好的前段资源包上传到SVN上之后,相关的图片.CSS.js文件要换成线上地址给后台开发. 2:图片-----压缩(https://tinypng.c ...
- 前端开发笔记(1)html基础
HTML介绍 HTML是HyperTextMarkupLanguage超文本标记语言的缩写 HTML是标记语意的语言 编辑器 任何纯文本编辑器都能够编辑html,比如记事本,editplus,note ...
- Web前端开发最佳实践系列文章汇总
Web前端开发最佳实践(1):前端开发概述 Web前端开发最佳实践(2):前端代码重构 Web前端开发最佳实践(3):前端代码和资源的压缩与合并 Web前端开发最佳实践(4):在页面中添加必要的met ...
- Web前端开发最佳实践(9):CSS代码太太乱,重复代码太多?你需要精简CSS代码
前言 提高网站整体加载速度的一个重要手段就是提高代码文件的网络传输速度.之前提到过,所有的代码文件都应该是经过压缩了的,这可提高网络传输速度,提高性能.除了压缩代码之外,精简代码也是一种减小代码文件大 ...
- Web前端开发最佳实践(7):使用合理的技术方案来构建小图标
大家都对网站上使用的小图标肯定都不陌生,这些小图标作为网站内容的点缀,增加了网站的美观度,提高了用户体验,可是你有没有看过在这些网站中使用的图标都是用什么技术实现的?虽然大部分网站还是使用普通的图片实 ...
随机推荐
- 给生活加点惊喜,做创意生活的原型设计师丨编程挑战赛 x 选手分享
前言 做产品的大都跳过一个坑:我有了一个很好的产品创意,只差一个程序员帮我实现编程了. 事实上从产品创意到落地上线,中间需要经过非常复杂的过程,细节的逻辑流程才是难点,创意不能落地,并不值钱. 本文作 ...
- CSS 高阶小技巧 - 角向渐变的妙用!
本文将介绍一个角向渐变的一个非常有意思的小技巧! 我们尝试使用 CSS 绘制如下图形: 在之前,类似的图案,其实我们有尝试过,在 单标签实现复杂的棋盘布局 一文中,我们用单标签实现了这样一个棋盘布局: ...
- Kali中python问题
Kali中python问题 1.查看python有哪些版本 update-alternatives --display python 2.如果没有,可以去/usr/bin查看kali自带哪些版本 查看 ...
- 支付回调MQ消息的幂等处理及MD5字符串es中的使用及支付宝预授权完成
支付回调MQ消息的幂等处理及MD5字符串es中的使用及支付宝预授权完成 1.幂等的处理,根据对象的转json 转md5作为key,退款的处理 控制发送端?业务上比较难控制.支付异步通知,退款有同步通知 ...
- rocketMq和kafka对比
为什么在RocketMQ和kafka中选型 在单机同步发送的场景下,Kafka>RocketMQ,Kafka的吞吐量高达17.3w/s,RocketMQ吞吐量在11.6w/s. kafka高性能 ...
- 一次因生产事故与chatGpt的对话
一次因生产事故与chatGpt的对话 前言:生产出现了一个内存溢出的事故,记录错误信息.错误日志如下 org.springframework.web.util.NestedServletExcepti ...
- java -- Stringbuild, Date, Calendar
Stringbuild类 由于String类的对象内容不可改变,每次拼接都会构建一个新的String对象,既耗时,又浪费内存空间 这时需要通过java提供的StringBuild类解决这个问题 Str ...
- xcodebuild命令行工具使用详解
xcodebuild命令行工具使用 如何通过命令行编译ios项目? xcodebuild是一个命令行工具,允许你从命令行对Xcode项目和工作区执行编译.查询.分析.测试和归档操作.它对项目中包含的一 ...
- 源码安装slurm
一.源码安装munge 1.下载munge 下载地址:https://github.com/dun/munge/releases 2.安装编译 tar -Jxvf munge-0.5.15.tar.x ...
- 08列表(list)与元组(tuple)
列表(list)与元组(tuple) 列表的格式 >- [数据1,数据2,数据3,数据4,......] >- 列表可以存储多个数据,数据之间的逗号以英文分割而且可以数据是不同类型的数据, ...