先记住这个答案
@scope 是 CSS 原生的作用域机制。写法是 @scope (.root) { ... },花括号里的选择器只会匹配 .root 子树内的元素。还可以加 to(.boundary) 设一个下界,让样式不渗透到 .boundary 及其子树里。它不增加选择器的优先级权重,但引入了「作用域邻近度」的概念:当两条规则优先级完全相同时,离作用域根更近的那条胜出。
@scope (.root)限制选择器只在子树内匹配to()设下界,排除嵌套区域- 不改变优先级权重,只加「邻近度」比较
- 在
<style>标签里可省略根选择器
基本用法:给组件划定样式范围
以前想让 .card 内部的 <p> 样式不影响页面其他地方的 <p>,要么用 BEM 命名(.card__text),要么用 CSS Modules 编译。现在可以直接写 @scope。
.card 内匹配CSS@scope (.card) {
p {
margin: 0 0 12px;
line-height: 1.6;
}
.title {
font-size: 18px;
}
}
/* 页面其他地方的 p 和 .title 不受影响 */@scope (.card) 里的 p 选择器只会匹配 .card 子树里的 <p> 元素。页面其他地方的 <p> 不会被选中。选择器本身的优先级还是 (0,0,1),@scope 不会帮它加权重。
还有一种写法是在 <style> 标签上直接用 @scope,不写根选择器,这时作用域根就是 <style> 标签的父元素。这种方式适合在 HTML 组件模板里内联样式。
用 to() 排除嵌套区域
组件经常会嵌套。比如 .card 里面还有一个 .card,外层 @scope (.card) 的样式会穿透到内层。这时用 to() 设一个下界。
@scope (.card) to (.card) {
p { color: #333; }
}这条规则让 p { color: #333 } 只作用于外层 .card 到内层 .card 之间的 <p> 元素。内层 .card 里的 <p> 不受影响。下界元素本身也被排除在外。
这在实际项目里很实用。组件库里 .dropdown 可能嵌套 .dropdown(多级菜单),用 @scope (.dropdown) to (.dropdown) 就能确保每一级只管自己的样式。
邻近度在层叠中的位置
@scope 引入的「作用域邻近度」只在一个很窄的条件下起作用:两条规则的来源、!important、layer 顺序、选择器优先级全部相同时,才比较邻近度。离作用域根更近的规则胜出。
比如页面上有两个嵌套的 @scope,外层是 @scope (.page),内层是 @scope (.card)。如果两者都给 p 设了 color,选择器优先级都是 (0,0,1),那么 @scope (.card) 胜出,因为 .card 离目标 <p> 更近。
如果外层的选择器更具体(比如 .page .content p),它的优先级更高,邻近度就不起作用了。所以邻近度是个「兜底」机制,不能依赖它来覆盖样式。
容易答错的地方
- 以为
@scope会提升选择器优先级 @scope不增加任何优先级权重。@scope (.card) { p { color: blue; } }里的p优先级还是 (0,0,1),外部一个.text { color: red }就能覆盖它。想提升优先级要在选择器本身下功夫。- 以为
@scope能阻止外部样式穿透 @scope只限制自己写的选择器的匹配范围,不阻止外部选择器匹配@scope范围内的元素。想要真正的样式隔离要用 Shadow DOM。
面试官还会怎么问?
@scope 里能用 :scope 伪类做什么?
:scope 指代作用域根元素本身。比如 @scope (.card) { :scope { border: 1px solid #ddd; } } 就是给 .card 本身加边框。没有 :scope 的话,@scope 里的选择器只能匹配子代元素。
@scope 和 CSS 嵌套(&)有什么区别?
CSS 嵌套是语法糖,& 代表父选择器,展开后和平铺写法完全等价,不影响匹配范围。@scope 是真正限制了选择器的匹配范围,超出作用域的元素即使满足选择器也不会被选中。
浏览器兼容性怎么样?
Chrome 118、Edge 118、Safari 17.4 以上支持。Firefox 128 开始支持。IE 不支持。目前如果要兼容老浏览器,还是需要 BEM 命名或 CSS Modules 作为备选方案。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。