关于CSS @layer:探讨级联层如何解决样式优先级混乱。
@layer 不改变选择器权重,而是在权重之上插了一层"整层排序":样式先比层、再比权重,把第三方库、重置样式、业务代码分进不同层,谁赢由层的声明顺序决定,从此不必靠堆 !important 和无限加类名来压权重。
一、先搞清楚 @layer 站在级联的哪个位置
结论:CSS 的完整判定顺序是「来源与重要性 → 层顺序(@layer)→ 选择器权重 → 出现顺序」,@layer 排在权重前面,所以它能一次性压过权重的混乱。
这条顺序意味着:只要两条声明落在不同层,权重写多少都没用,层靠后的直接赢;只有落在同一层里,才回到我们熟悉的权重比拼。@layer 的语法主要有三个形态:
/* 1. 先声明层顺序(关键,顺序一旦定下来就不再变) */
@layer reset, vendor, base, components, utilities;
/* 2. 把样式塞进层 */
@layer base {
h1 { font-size: 1.5rem; }
}
/* 3. 嵌套子层,用点号 */
@layer vendor.ui { .btn { padding: 4px 12px; } }
层顺序由「第一次出现」决定,后面再写一遍同名层不会重新排序。所以惯例是文件顶部用一行 @layer a, b, c; 先把顺序钉死,各模块文件只管往里填内容。
二、最实用的三档用法:第三方、自定义、业务
结论:把别人的 CSS 关进低优先级层,是 @layer 投入产出比最高的用法。
第一档,引入外部库时直接指定层,不用再改它的源码,也不用 !important:
@import url("bootstrap.css") layer(vendor);
@layer vendor, app; /* app 在后,天然比 vendor 赢 */
注意 @import 必须写在其它规则之前(@layer 语句和 @charset 除外),否则整条被忽略;生产环境仍建议打包时合并,别在线上留一堆 @import。
第二档,自己项目分层。推荐五层:reset(归零)、base(基础元素样式)、layout(栅格与容器)、components(组件)、utilities(工具类)。工具类放最后一层,就不必写成 .card .title.text-red 这种长链了。
第三档,revert-layer 关键词。它的作用是"撤销当前层的这条声明,回退到上一层里的值",适合写组件默认值——上层想覆盖时覆盖,不想管时下层自己兜底。
三、两个最容易翻车的坑
结论:未分层样式的优先级在正常声明里最高、在 !important 里最低,这是 90% 的人踩坑的根源。
坑一,只把 reset 放进层,其余全留在层外:因为未分层的正常声明优先于所有分层声明,你会发现 @layer 好像"没生效"。要么全都进层,要么接受这个规则并刻意利用它。
坑二,!important 会反转整层的胜负关系。正常声明里"层越靠后越赢",但带 !important 时变成"层越靠前越赢",且未分层的 !important 反而最弱。这个反转是官方设计(为了让低层作者的兜底声明能压住上层),但它反直觉,排查时先怀疑它。
另外,匿名层 @layer { ... } 无法被后续引用,只适合一次性打包第三方代码;@layer 目前 Chrome 99+、Firefox 97+、Safari 15.4+ 起支持,属于广泛可用能力。
四、老项目怎么渐进迁移
结论:不用推倒重来,因为未分层样式赢过分层样式,你可以安全地先引入层、后逐步搬代码。
第一步,在入口文件顶部只写一行 @layer legacy, app;,此时所有老代码都在 legacy 层之外,行为完全不变。第二步,把新写的组件放进 app 层——它比老代码低,初期会被老样式压住,需要覆盖时再把那条老规则搬进 legacy。第三步,边改边搬,直到老代码全部收进 legacy,最后把 legacy 从顺序声明里删掉。整个过程线上可以随时发布,没有"大爆炸式重构"。
说到底,@layer 解决的不是"权重算不明白",而是"权重不该承担架构职责"。层顺序是写在文件开头、一眼能看懂的架构决策;权重是散落在几百个选择器里的局部细节。把前者显式化,后者就回归它本来的位置。
转载请注明出处,版权归原作者所有。
正式会员
认证极客






