← 返回场景决策
1. Flex vs Grid — 布局方案决策
Flex = 排队
→
一维排列,横着排或竖着排,简单灵活
Grid = 停车场
→
二维网格,行和列同时控制,精确到位
✅ 什么时候用 Flex
- 导航栏:Logo + 菜单横排 — flex + justify-content: space-between
- 按钮组:一排按钮 — flex + gap
- 卡片内布局:图标 + 文字 + 操作 — flex + align-items: center
- 单行/单列布局:只要一个方向的排列 — 一维就够了
- 内容不固定:不知道有多少元素 — flex 自动换行或缩小
✅ 什么时候用 Grid
- 整体页面布局:header + sidebar + main + footer — grid-template-areas
- 卡片网格:3×4 的商品展示 — grid-template-columns: repeat(3, 1fr)
- 仪表盘:不同大小模块拼在一起 — grid-row/column span
- 固定行列:已知行列数 — Grid 布局更精确
- 对齐要求严格:行列都要精确对齐 — Grid 原生支持
📊 Flex vs Grid 决策表
| 维度 | Flex | Grid |
| 维度 | 一维(行或列) | 二维(行+列同时) |
| 内容驱动 vs 布局驱动 | 内容驱动(弹性适应内容) | 布局驱动(先定义格子再填内容) |
| 导航栏 | ✅ 最佳 | 杀鸡用牛刀 |
| 卡片网格 | 可用但不精确 | ✅ 最佳 |
| 页面骨架 | 嵌套太深 | ✅ grid-template-areas |
| 动态数量元素 | ✅ 自动换行 | 需改 repeat |
| 精确行列对齐 | 难 | ✅ 原生支持 |
💻 页面骨架 — Grid Template Areas
/* 经典后台布局 */
.layout {
display: grid;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
grid-template-columns: 240px 1fr;
grid-template-rows: 60px 1fr 40px;
min-height: 100vh;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }
/* 响应式:移动端侧边栏折叠 */
@media (max-width: 768px) {
.layout {
grid-template-areas:
"header"
"main"
"footer";
grid-template-columns: 1fr;
}
.sidebar { display: none; }
}
面试金句
grid-template-areas 是 CSS 中唯一能"画图式"定义布局的属性——直接用 ASCII 字符画布局图,HTML 结构只需绑定 grid-area 名称即可。响应式只需改变 areas 布局,无需改 HTML 结构,这是 Grid 相比 Flex 做页面骨架的核心优势。
💻 导航栏 — Flex 最佳场景
/* 简单又强大的导航栏 */
.navbar {
display: flex;
align-items: center; /* 垂直居中 */
justify-content: space-between; /* 两端对齐 */
padding: 0 24px;
height: 60px;
}
.navbar-links {
display: flex;
gap: 16px; /* 替代 margin 的现代方案 */
}
/* <nav class="navbar">
<div class="logo">Logo</div>
<div class="navbar-links">
<a>首页</a> <a>关于</a> <a>联系</a>
</div>
</nav> */
面试金句
gap 是 Flexbox 的现代间距方案,替代了传统的 margin 嵌套(最后一个元素需 :last-child 去 margin)。justify-content: space-between 让首尾元素分别贴左右边缘、中间元素均分,是导航栏 Logo 在左、菜单在右的经典布局。
2. BFC — 块格式化上下文决策
BFC = 独立办公室
→
里面的东西不管怎么翻,不影响外面
没有BFC = 开放办公区
→
别人的东西会飘到你的桌上(margin塌陷)
✅ 什么时候需要创建 BFC
- 清除浮动:子元素 float 后父元素高度塌陷 — overflow: hidden 创建 BFC
- 防止 margin 合并:相邻块元素 margin-top 和 margin-bottom 合并 — 创建 BFC 隔离
- 文字不环绕浮动元素:文字不该绕着 float 图片走 — 给文字容器创建 BFC
- 自适应布局:避免 float 元素覆盖正常流 — BFC 区域不会与 float 重叠
❌ 什么时候不要滥用 BFC
- overflow: hidden 切断内容:有下拉菜单/弹窗的容器不能用 overflow:hidden
- 用 BFC 代替 Flex:现代布局优先用 Flex/Grid,别用 float + BFC
📊 创建 BFC 的方式
| 方式 | 推荐度 | 副作用 | 适用场景 |
| overflow: hidden/auto | ✅ 最常用 | 可能切断内容 | 清除浮动 |
| display: flow-root | ✅ 最佳 | 无副作用 | 现代浏览器首选 |
| display: flex/grid | ✅ | 本身就是BFC | 布局顺便解决 |
| float: left/right | ❌ 不推荐 | 自身脱离文档流 | 别用了 |
| position: absolute/fixed | ❌ 不推荐 | 脱离文档流 | 别用了 |
💻 BFC 清除浮动对比
/* ❌ 问题:子元素浮动后父元素高度为0 */
.parent {
border: 1px solid red;
/* 高度塌陷!看不到边框 */
}
.child { float: left; width: 100px; height: 100px; }
/* ✅ 方案1:overflow 创建 BFC */
.parent {
border: 1px solid green;
overflow: hidden; /* 或 auto */
}
/* ✅ 方案2:display: flow-root(最干净) */
.parent {
border: 1px solid blue;
display: flow-root; /* 无副作用,专为BFC设计 */
}
/* ✅ 方案3:现代做法——直接用 Flex */
.parent {
display: flex; /* Flex 本身就是 BFC,顺便解决布局 */
}
面试金句
display: flow-root 是 W3C 为清除浮动专门设计的 BFC 创建方式,无 overflow:hidden 的内容截断副作用。现代开发中,float 布局已被 Flex/Grid 取代,BFC 清除浮动的主要价值在于理解 CSS 渲染机制(面试高频),实际编码中直接用 Flex 即可。
3. position 定位方案决策
static = 站队
→
默认位置,一个挨一个
relative = 站队但稍微挪
→
还在队伍里,但歪了点
absolute = 出队站到指定位置
→
脱离队伍,找最近的 relative 祖先对齐
fixed = 钉在屏幕上
→
相对于浏览器窗口,滚动也不动
sticky = 平时站队,到位置就粘住
→
滚动到阈值前是 static,到后变 fixed
📊 position 决策表
| 值 | 脱离文档流 | 参照物 | 典型场景 |
| static | ❌ | 无(正常流) | 默认,不需要定位 |
| relative | ❌(还占位) | 自身原始位置 | 为 absolute 子元素提供参照 |
| absolute | ✅ | 最近的非 static 祖先 | 下拉菜单、Tooltip、Badge |
| fixed | ✅ | 浏览器视口 | 固定导航栏、回到顶部 |
| sticky | ❌→✅(动态) | 最近的滚动祖先 | 表头吸顶、侧边栏粘滞 |
💻 sticky 吸顶表头
/* 表头滚动到顶部时吸住 */
.table-header {
position: sticky;
top: 0; /* 距顶部0px时开始粘滞 */
z-index: 10; /* 确保在内容上方 */
background: #fff; /* 必须设背景,否则内容会透过来 */
}
/* ⚠️ sticky 失效的常见原因 */
// 1. 父元素有 overflow: hidden/auto/scroll → sticky 失效
// 2. 父元素高度不够 → 没有滚动空间
// 3. 没设置 top/bottom → 不知道粘在哪里
面试金句
position: sticky 是 relative 和 fixed 的混合体:在阈值前表现为 relative,到达 top/left 阈值后表现为 fixed,但不会脱离父容器。最常见坑:父元素设了 overflow: hidden/auto/scroll 会导致 sticky 失效,因为 sticky 的参照物是最近的可滚动祖先。
4. 居中方案 — 万年面试题决策
居中 = 画框挂画
→
水平居中=左右对齐,垂直居中=上下对齐
📊 居中方案决策表
| 方案 | 代码 | 适用场景 | 兼容性 |
| Flex 居中 ⭐ | display:flex; justify-content:center; align-items:center; | 通用,首选 | 99% |
| Grid 居中 ⭐ | display:grid; place-items:center; | 最简洁 | 97% |
| 绝对定位 + transform | position:absolute; top:50%; left:50%; transform:translate(-50%,-50%) | 已知/未知宽度都行 | 98% |
| 绝对定位 + margin:auto | position:absolute; inset:0; margin:auto; | 需要已知宽高 | 98% |
| 行内元素 | text-align:center; line-height=height | 单行文字 | 100% |
💻 各种居中实现
/* ✅ 方案1:Flex 居中 — 最推荐 */
.container {
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
height: 100vh;
}
/* ✅ 方案2:Grid 居中 — 最简洁 */
.container {
display: grid;
place-items: center; /* 一行搞定!*/
height: 100vh;
}
/* ✅ 方案3:绝对定位 + transform */
.container { position: relative; height: 100vh; }
.child {
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
}
/* ✅ 方案4:绝对定位 + margin auto(需已知宽高)*/
.child {
position: absolute;
inset: 0; /* top:0; right:0; bottom:0; left:0 的简写 */
margin: auto;
width: 200px;
height: 100px;
}
面试金句
居中方案的选择优先级:Grid place-items(最简,1 行)> Flex justify+align(最通用)> 绝对定位 + transform(兼容性好)> margin:auto(需已知宽高)。transform: translate(-50%, -50%) 基于元素自身尺寸计算百分比,所以不受宽高是否已知的影响。
⚠️ 面试答题顺序:先说 Flex(最现代)→ Grid(最简洁)→ 绝对定位 + transform(兼容)→ margin auto(经典),最后说 line-height 只适用于行内元素。
5. 响应式布局决策
响应式 = 一件衣服多个尺码
→
同一款设计,S/M/L 不同版型
✅ 响应式方案选择
- Media Query:断点式布局 — 最传统、最精确
- Flex 换行:flex-wrap 自动换行 — 最自然的响应式
- Grid auto-fit:grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)) — 最优雅
- Container Query:根据容器宽度而非视口 — 最精准(2023+)
- clamp():font-size: clamp(14px, 2vw, 18px) — 流式缩放
📊 响应式方案决策表
| 方案 | 参照物 | 最佳场景 | 浏览器支持 |
| Media Query | 视口宽度 | 整体页面布局切换 | 100% |
| Flex 换行 | 容器宽度 | 卡片列表自动换行 | 99% |
| Grid auto-fit | 容器宽度 | 不等宽卡片网格 | 97% |
| Container Query | 父容器宽度 | 组件级响应式 | 92% |
| clamp() | 视口宽度 | 字号/间距流式缩放 | 97% |
💻 Grid auto-fit 自适应卡片
/* 不用 media query,自动响应式!*/
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 24px;
}
/* 容器宽度 > 1120px → 4列 */
/* 容器宽度 840-1120px → 3列 */
/* 容器宽度 560-840px → 2列 */
/* 容器宽度 < 560px → 1列 */
/* clamp() 流式字号 */
h1 {
font-size: clamp(24px, 5vw, 48px);
/* 最小24px,理想5vw,最大48px */
}
面试金句
repeat(auto-fit, minmax(280px, 1fr)) 是 CSS 最优雅的响应式方案:auto-fit 让浏览器根据可用空间自动计算列数,minmax(280px, 1fr) 设定每列最小 280px、最大平分剩余空间。零 media query 实现自适应卡片网格。clamp(24px, 5vw, 48px) 实现字号流式缩放(最小值、理想值、最大值)。
💻 Container Query 组件级响应式
/* 父容器声明查询容器 */
.sidebar { container-type: inline-size; }
/* 根据父容器宽度(不是视口宽度)变化 */
@container (min-width: 400px) {
.card {
display: flex; /* 宽了就横排 */
gap: 16px;
}
}
@container (max-width: 399px) {
.card {
display: block; /* 窄了就竖排 */
}
}
/* 同一个 .card 组件,放在 sidebar 里是竖排,放在 main 里是横排 */
/* 不用关心视口宽度,只关心自己所在容器宽度!*/
面试金句
Container Query(@container) 是 2023 年所有主流浏览器正式支持的新特性,实现了组件级响应式:Media Query 基于视口宽度,Container Query 基于父容器宽度。解决了同一个组件在不同位置(侧边栏 300px vs 主区域 800px)需要不同布局的问题,是组件化开发的关键基础设施。
6. CSS 动画方案决策
transition = 开关门
→
只有两个状态,从A到B平滑过渡
animation = 连续剧
→
多个关键帧,可以循环播放
✅ 什么时候用 transition
- hover 效果:按钮变色、卡片浮起 — 两个状态之间过渡
- 展开/折叠:max-height 从 0 到 auto — 简单状态变化
- 显示/隐藏:opacity 0→1 — 淡入淡出
- 输入焦点:border-color 变化 — 简单属性过渡
✅ 什么时候用 animation
- 循环动画:loading 旋转、脉冲呼吸 — infinite 循环
- 多关键帧:弹跳效果、复杂路径 — 多个百分比状态
- 自动播放:不需要触发条件 — 页面加载就播放
- 序列动画:先变色再移动再缩放 — 多步骤
📊 动画方案决策表
| 方案 | 触发方式 | 关键帧 | 循环 | 性能 | 场景 |
| transition | 状态变化触发 | 2个(起止) | ❌ | 好 | hover/展开 |
| animation + @keyframes | 自动/触发 | 多个 | ✅ | 好 | loading/弹跳 |
| requestAnimationFrame | JS手动 | 任意 | ✅ | 最好 | 游戏/物理动画 |
| Web Animations API | JS控制 | 多个 | ✅ | 好 | JS精确控制动画 |
💻 高性能动画属性选择
/* ✅ 只改这些属性 → GPU 加速,60fps */
.fast-animation {
transform: translateX(100px); /* 移动 */
opacity: 0.5; /* 透明度 */
}
/* ❌ 改这些属性 → 触发重排重绘,卡顿 */
.slow-animation {
left: 100px; /* ❌ 触发 layout */
top: 100px; /* ❌ 触发 layout */
width: 200px; /* ❌ 触发 layout */
margin: 20px; /* ❌ 触发 layout */
}
/* 性能等级:transform/opacity > box-shadow > color/background > width/height/margin */
面试金句
浏览器渲染流水线:JavaScript → Style → Layout → Paint → Composite。transform 和 opacity 只触发 Composite 阶段(GPU 合成层),跳过 Layout 和 Paint,所以是唯一能保证 60fps 的动画属性。width/top/left/margin 触发 Layout(重排),导致整棵渲染树重新计算,是动画卡顿的元凶。
💻 常用 CSS 动画
/* Loading 旋转 */
@keyframes spin {
to { transform: rotate(360deg); }
}
.spinner {
animation: spin 1s linear infinite;
}
/* 脉冲呼吸 */
@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.05); opacity: 0.8; }
}
.pulse { animation: pulse 2s ease-in-out infinite; }
/* 弹入效果 */
@keyframes bounceIn {
0% { transform: scale(0.3); opacity: 0; }
50% { transform: scale(1.05); }
70% { transform: scale(0.9); }
100% { transform: scale(1); opacity: 1; }
}
.bounce-in { animation: bounceIn 0.5s ease-out; }
面试金句
@keyframes 定义关键帧动画,百分比(0%/50%/100%)控制各阶段状态。animation 简写:duration | timing-function | delay | iteration-count | direction。ease-in-out 模拟物理减速(先加速后减速),linear 匀速适合旋转。所有动画只操作 transform 和 opacity,保证 GPU 加速。
7. CSS 变量 — 主题与设计系统决策
CSS 变量 = 全局配置文件
→
改一处,所有引用它的地方都跟着变
SCSS 变量 = 编译时替换
→
编译后就写死了,运行时不能改
✅ 什么时候用 CSS 变量 (--var)
- 主题切换:暗色/亮色模式 — 改 :root 变量,全局生效
- 设计系统:颜色/间距/字号统一管理 — 一处定义处处引用
- 运行时变化:用户可调节的配置 — JS 动态改 CSS 变量
- 组件主题:组件级作用域 — .card { --card-padding: 16px }
❌ 什么时候用 SCSS 变量 ($var)
- 编译时计算:颜色变暗/变亮 — SCSS darken/lighten 函数
- 不需要运行时改变:值在编译时确定 — 静态设计 Token
- 复用复杂计算:@mixin + $var — 条件逻辑在编译时处理
💻 暗色/亮色主题切换
/* 亮色主题(默认)*/
:root {
--bg: #ffffff;
--text: #1a1a1a;
--primary: #3b82f6;
--card-bg: #f3f4f6;
--border: #e5e7eb;
}
/* 暗色主题 */
[data-theme="dark"] {
--bg: #F7F6F3;
--text: #cdd6f4;
--primary: #58a6ff;
--card-bg: #FFFFFF;
--border: #30363d;
}
body {
background: var(--bg);
color: var(--text);
transition: background 0.3s, color 0.3s;
}
// JS 切换主题
document.documentElement.dataset.theme = isDark ? 'dark' : 'light';
// 或者跟随系统
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
面试金句
CSS 变量(Custom Property)实现主题切换的核心优势:运行时可修改(SCSS 变量编译后写死)。通过 [data-theme="dark"] 选择器覆盖 :root 变量,所有引用 var() 的元素自动更新。prefers-color-scheme media query 可跟随操作系统暗色模式,实现无感知主题适配。
8. 语义化标签 — HTML 结构决策
语义化 = 贴标签的文件柜
→
每个抽屉写清楚里面是什么,找人一目了然
div 汤 = 没标签的杂物箱
→
全是 div,不知道哪个是导航哪个是内容
✅ 语义化标签的好处
- SEO:搜索引擎理解页面结构 — header/nav/main/article 权重不同
- 无障碍:屏幕阅读器根据语义导航 — 盲人用户能跳转到导航区
- 可维护性:代码自解释 — <nav> 比 <div class="nav"> 更清晰
- 浏览器默认:语义标签自带无障碍角色 — 不用手写 role="navigation"
📊 语义化标签对照表
| 语义标签 | 替代的 div | 含义 | 使用频率 |
| <header> | <div class="header"> | 页头/区域头 | ⭐⭐⭐ |
| <nav> | <div class="nav"> | 导航区域 | ⭐⭐⭐ |
| <main> | <div class="content"> | 主内容(每页1个) | ⭐⭐⭐ |
| <article> | <div class="post"> | 独立内容块 | ⭐⭐ |
| <section> | <div class="section"> | 内容分区 | ⭐⭐⭐ |
| <aside> | <div class="sidebar"> | 侧边栏/附属内容 | ⭐⭐ |
| <footer> | <div class="footer"> | 页脚/区域尾 | ⭐⭐⭐ |
| <figure> | <div class="image-wrapper"> | 图片/图表容器 | ⭐ |
| <time> | <span class="date"> | 日期时间 | ⭐ |
💻 语义化页面结构
<!-- ✅ 语义化结构 -->
<header>
<nav aria-label="主导航">
<a href="/">首页</a>
<a href="/about">关于</a>
</nav>
</header>
<main>
<article>
<h1>文章标题</h1>
<time datetime="2024-01-15">2024年1月15日</time>
<p>文章内容...</p>
<figure>
<img src="chart.png" alt="数据图表">
<figcaption>图1:月度增长趋势</figcaption>
</figure>
</article>
<aside>
<h2>相关推荐</h2>
...
</aside>
</main>
<footer>
<p>版权信息</p>
</footer>
<!-- ❌ div 汤 -->
<div class="header">
<div class="nav">...</div>
</div>
<div class="content">
<div class="post">...</div>
<div class="sidebar">...</div>
</div>
面试金句
语义化标签的核心价值不在于样式(CSS 可以给 div 加相同样式),而在于机器可读性:搜索引擎爬虫、屏幕阅读器、浏览器插件通过语义标签理解页面结构。<main> 每页只能有一个,<article> 表示可独立分发的内容(文章/评论),<section> 表示 thematic grouping。
9. CSS-in-JS vs CSS Modules — 样式方案决策
CSS-in-JS = 内联定制西装
→
量体裁衣,完美贴合,但贵(运行时开销)
CSS Modules = 品牌成衣
→
批量生产,自动分类(hash类名),性价比高
原生 CSS = 基础款
→
简单直接,Vue SFC scoped 天然隔离
📊 样式方案决策表
| 方案 | 运行时 | 作用域 | 动态样式 | 框架绑定 | 推荐度 |
| Vue SFC scoped | 无 | 编译时隔离 | v-bind() 有限 | Vue | ⭐⭐⭐ |
| CSS Modules | 无 | 编译时hash | 有限 | 无关 | ⭐⭐⭐ |
| Tailwind CSS | 无 | 原子化 | 有限 | 无关 | ⭐⭐⭐ |
| Styled Components | 有 | 运行时hash | ✅ 强 | React | ⭐⭐ |
| Emotion | 有 | 运行时hash | ✅ 强 | React | ⭐⭐ |
| 全局 CSS | 无 | 无隔离 | 有限 | 无关 | ⭐ |
💻 Vue 3 SFC scoped 样式(最推荐)
<style scoped>
/* 自动添加 data-v-xxx 属性,样式只对当前组件生效 */
.card {
padding: 16px;
border-radius: 8px;
}
.card:hover {
box-shadow: 0 4px 12px rgba(0,0,0,.1);
}
</style>
<style>
/* 不加 scoped 的 style 块 = 全局样式 */
/* 适合引入第三方库的全局样式覆盖 */
.el-dialog { border-radius: 12px; }
</style>
面试金句
Vue <style scoped> 在编译时给当前组件的每个元素添加 data-v-xxxxxx 属性,CSS 选择器自动追加属性选择器(.card → .card[data-v-xxx]),实现组件级样式隔离。无运行时开销,是 Vue 项目首选样式方案。可在 scoped 中用 :deep(.child) 穿透到子组件。
💻 CSS Modules (React/Vite)
// 文件名必须带 .module.css
// Button.module.css
.btn {
padding: 8px 16px;
border-radius: 6px;
}
.primary {
background: #3b82f6;
color: white;
}
// Button.jsx
import styles from './Button.module.css';
function Button({ variant, children }) {
return (
<button className={`${styles.btn} ${styles[variant]}`}>
{children}
</button>
);
}
面试金句
CSS Modules 在构建时将类名编译为带 hash 的唯一名(.btn → .Button_btn_1a2b3c),天然实现样式隔离。与 CSS-in-JS 的区别:CSS Modules 无运行时开销(编译时完成),CSS-in-JS(Styled Components)在运行时动态生成样式。React 项目首选 CSS Modules 或 Tailwind,Vue 项目用 SFC scoped。
10. 伪元素 vs 伪类 — 选择器决策
伪类 = 状态探测器
→
检测元素当前状态(hover/focus/first-child)
伪元素 = 隐形替身
→
创建一个不存在的DOM元素(::before/::after)
✅ 什么时候用伪元素 (::before / ::after)
- 装饰性元素:不需要 DOM 节点的装饰线、图标 — 纯展示
- 清除浮动:.clearfix::after { content: ''; display: block; clear: both; }
- 图标叠加:required 表单字段的 * 号标记
- 三角形:CSS 三角形 — border 技巧
- 计数器:::before { content: counter(section) ". "; }
✅ 什么时候用伪类 (:hover / :focus 等)
- 交互状态::hover, :focus, :active — 用户操作反馈
- 结构选择::first-child, :nth-child(2n) — 选择特定子元素
- 表单状态::checked, :disabled, :valid — 表单验证反馈
- 否定选择::not(.active) — 排除某些元素
💻 伪元素实战
/* 必填字段 * 标记 */
.required::after {
content: '*';
color: red;
margin-left: 4px;
}
/* CSS 三角形 */
.tooltip::after {
content: '';
position: absolute;
bottom: -8px;
left: 50%;
transform: translateX(-50%);
border: 8px solid transparent;
border-top-color: #333; /* 只有顶边有色 = 三角形 */
}
/* 序号计数器 */
ol { counter-reset: section; }
li { counter-increment: section; }
li::before { content: counter(section) ". "; font-weight: bold; }
/* 自定义 checkbox(隐藏原生 + 伪元素替代)*/
input[type="checkbox"] { display: none; }
input[type="checkbox"] + label::before {
content: '';
display: inline-block;
width: 18px; height: 18px;
border: 2px solid #999;
border-radius: 3px;
margin-right: 8px;
vertical-align: middle;
}
input[type="checkbox"]:checked + label::before {
background: #3b82f6;
border-color: #3b82f6;
}
面试金句
::before 和 ::after 通过 content 属性在 DOM 中插入"虚拟元素"——不改变 HTML 结构,不影响 SEO。CSS 计数器(counter-reset / counter-increment) 可在纯 CSS 中实现自动编号。自定义 checkbox 技巧:隐藏原生 input + label::before 创建替代视觉 + :checked 伪类切换样式,实现完全可定制的表单控件。
💭 记忆口诀:伪类一个冒号(:hover),伪元素两个冒号(::before)。伪类选状态,伪元素造东西。