前置知识
样式文件
样式文件(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&type=style&index=0&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&type=style&index=0&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&type=style&index=0&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
};