【css】作用域隔离

74 阅读5分钟

前置知识

样式文件
样式文件(css/less/scss)
 └── 样式规则(rule)
      ├── 选择器(selector)
      └── 声明块(declaration block)
            └── 样式声明(declaration)
                  ├── 属性(property)
                  └── 属性值(value)
// global.scss
.cls {
  color: red;
}
选择器
  • 基础选择器
    • 标签选择器
      • 选取指定 html 标签名称的元素
      • p { color: red; }
    • 类选择器
      • 选取带有指定 class 属性的元素
      • .box { color: red; }
    • ID 选择器
      • 选取具有指定 id 属性的元素
      • #box { color: red; }
    • 通配选择器 *
      • 选取所有元素
      • * { color: red; }
  • 组合选择器
    • 由基础选择器构成
    • 后代选择器
      • 用空格分隔
      • 可多层嵌套
      • .a .b .c {color: red; }
    • 子选择器
      • > 分隔
      • 仅匹配直接子元素
    • 相邻兄弟选择器
      • + 分隔
      • 匹配紧接某元素后的相邻兄弟元素
    • 通用兄弟选择器
      • ~ 分隔
      • 匹配某元素后的所有兄弟元素
  • 属性选择器
    • 用于根据 HTML 元素的属性及其值来选中元素
    • [target] {}
    • a[target="_blank"] {}
  • 其他选择器
    • 伪类选择器
      • :hover 鼠标悬停
      • :focus 获取焦点
    • 伪元素选择器
      • ::before 在元素内容前插入伪元素
      • ::after 在元素内容后插入伪元素
层叠规则

当多个样式规则作用于同一个元素时,浏览器如何决定最终应用哪一条规则的机制

  • 重要性(Importance)
    • !important 优先级最高
  • 样式来源(Origin)
    • 用户样式:由网站访问者自己设置的样式
    • 作者样式:由网页开发者编写的 CSS
      • 内联样式(<div style="color: red;">
      • 内部样式表(<style> ... </style>
      • 外部样式表(<link rel="stylesheet" href="style.css">
    • 浏览器默认样式:浏览器内置的默认 CSS 规则
  • 优先级(Specificity)
    • 内联样式(1000)
    • ID 选择器(0100)
    • 类/属性/伪类选择器(0010)
    • 元素/伪元素选择器(0001)
  • 来源顺序(Source Order)
    • 优先级相同,后定义的规则生效
  • 继承(Inheritance)
    • 只有部分属性会继承,且继承优先级最低
样式的应用
  • 浏览器解析 HTML,生成 DOM 树
    • DOM 树表示页面的结构和内容
  • 浏览器解析 CSS 文件生成 CSSOM(CSS 对象模型)
    • CSSOM 记录了所有的样式规则(包括选择器和声明块)
    • 控制台内输入 document.styleSheets 可查
  • 样式匹配(Selector Matching)
    • 浏览器会遍历 CSSOM 中的样式规则
    • 根据规则的选择器,在 DOM 树中查找所有匹配的元素
  • 样式应用与合并
    • 应用
      • 找到匹配的元素后,将规则的样式应用到元素上
    • 合并:
      • 如果有多个规则匹配同一个元素
      • 浏览器会根据优先级(权重)和层叠规则合并样式
      • 得到最终的“计算样式”(Computed Style)
    • 简单理解
      • 应用与合并的过程就是为浏览器内部生成临时数据的过程
      • 临时数据是一个对象
      • 以 dom 为 key
      • 以最终样式为 value
  • 生成渲染树并渲染页面
    • 浏览器根据包含最终样式的临时数据和 DOM 结构生成渲染树
    • 根据渲染树绘制页面

样式冲突

组件结构

parent.vue
├── child.vue
└── child2.vue

组件内的 style 标签在开发模式会以内联样式表的形式添加到 head

<head>
  <style type="text/css" data-vite-dev-id="D:/project/self/web-proj/src/views/home/child.vue?vue&amp;type=style&amp;index=0&amp;lang.scss">
    .tst {
      background-color: #f00;
    }
  </style>
  <style type="text/css" data-vite-dev-id="D:/project/self/web-proj/src/views/home/child2.vue?vue&amp;type=style&amp;index=0&amp;lang.scss">
    .tst {
      background-color: #000;
    }
  </style>
  <style type="text/css" data-vite-dev-id="D:/project/self/web-proj/src/views/home/parent.vue?vue&amp;type=style&amp;index=0&amp;lang.scss">
    .tst {
      background-color: #fff;
    }
  </style>
</head>

组件内的 style 标签在生产模式会以外部样式表的形式添加到 head

<head>
  <link rel="stylesheet" crossorigin="" href="/assets/child-CBpvDNOK.css">
  <link rel="stylesheet" crossorigin="" href="/assets/child2-BPfBOJzd.css">
  <link rel="stylesheet" crossorigin="" href="/assets/parent-CUQ5VyXB.css">
</head>

根据层叠规则中来源顺序,优先级相同,后定义的规则生效,上述三个组件应用了 tst 类选择器的元素背景色都为白色,由此引出了样式冲突的问题

作用域隔离

  • 为了解决组件中样式冲突的问题,引入了作用域隔离的概念
  • 作用域隔离就是组件内 style 标签中的样式只作用于当前组件,而不会影响到其他组件
  • 在 vue 中作用域隔离主要是通过 scoped
  • 在 react 中作用域隔离主要是通过 css module

样式作用域 scoped

<div class="login">登录</div>
<style scoped>
.login {
  width: 100px;
  height: 100px;
}
</style>
设置 scoped 后构建
  • 构建过程中
    • Vue 的编译器会为每个组件生成一个独特的哈希
    • 会给组件中所有 dom 元素添加了一个属性 data-v-hash
    • 在样式规则的选择器后增加了一个属性选择器
      • .cls #id > div => .cls #id > div[data-v-hash]
  • 通过上述方式使得样式只作用于含有该属性的 dom 元素
<div data-v-257dda99b class="login">登录</div>
<style>
.login[data-v-257dda99b] {
  width: 100px;
  height: 100px;
}
</style>
深度选择器
>>>、/deep/、::v-deep
  • 使用场景
    • 页面内使用组件时需要临时修改组件的样式
  • 原理
    • 深度选择器在组件样式作用域 <style scoped> 中为深度选择器的前面的一个选择器增加一个属性选择器 [data-v-hash]
    • .cls #id >>> div => .cls #id[data-v-e0f690c0] div

类名哈希化 css module

  • 在 Vite 项目中引入 .module.css 文件时,Vite 会自动将其识别为 CSS Module
    • 使用 PostCSS 的 postcss-modules 插件对 CSS 进行处理
  • 处理过程中
    • 所有的类名都会被转换为一个独一无二的、带有哈希值的类名
    • 会自动生成一个 JS 对象,将原始类名映射到哈希化后的类名
/* src/tst.module.css */
.a .b {
    color: red;
}

// =>

/* src/tst.module.css?t=1751855060747 */
const __vite__id = "D:/project/react-pro/src/tst.module.css"
const __vite__css = "._a_1thw1_1 ._b_1thw1_1 {\r\n    color: red;\r\n}"

// 将 CSS 内容插入或更新到 <style> 标签中,并放入 <head> 标签内
__vite__updateStyle(__vite__id, __vite__css)

export const a = "_a_1thw1_1";
export const b = "_b_1thw1_1";

// 导出类名映射
export default {
	a: a,
	b: b
};