主题
CSS
说明
共 15 题,难度 ⭐ ~ ⭐⭐⭐,覆盖布局、定位、动画性能、响应式、CSS 新特性等前端高频 CSS 面试知识点。
1. 水平垂直居中的 N 种方式 ⭐
列举至少 5 种水平垂直居中的方案,并说明各自适用场景。
考察点:Flex / Grid / 定位 / transform / 表格布局
方案汇总
css
/* ① Flex(最常用) */
.parent {
display: flex;
justify-content: center;
align-items: center;
}
/* ② Grid */
.parent {
display: grid;
place-items: center;
}
/* ③ 绝对定位 + transform */
.parent {
position: relative;
}
.child {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
/* ④ 绝对定位 + margin auto(需已知宽高) */
.parent {
position: relative;
}
.child {
position: absolute;
inset: 0;
margin: auto;
width: 200px;
height: 100px;
}
/* ⑤ 绝对定位 + 负 margin(需已知宽高) */
.parent {
position: relative;
}
.child {
position: absolute;
top: 50%;
left: 50%;
width: 200px;
height: 100px;
margin-top: -50px;
margin-left: -100px;
}
/* ⑥ 行内元素居中 */
.parent {
text-align: center;
line-height: 300px; /* 等于容器高度 */
}
.child {
display: inline-block;
vertical-align: middle;
line-height: normal;
}
/* ⑦ table-cell */
.parent {
display: table-cell;
vertical-align: middle;
text-align: center;
}
.child {
display: inline-block;
}对比
| 方案 | 是否需要已知宽高 | 兼容性 | 适用场景 |
|---|---|---|---|
| Flex | ❌ | IE11+ | 首选,几乎万能 |
Grid place-items | ❌ | 现代浏览器 | 代码最少,二维布局 |
| absolute + transform | ❌ | IE9+ | 不影响父级布局 |
| absolute + margin auto | ✅ | IE8+ | 弹窗、模态框 |
| absolute + 负 margin | ✅ | IE6+ | 兼容古老浏览器 |
| line-height | ❌(但父级需固定高度) | 全兼容 | 单行文本 |
| table-cell | ❌ | IE8+ | 多行文本垂直居中 |
追问延伸
margin: auto在 Flex 容器中的特殊行为?(自动吸收剩余空间)- 如何让一个不定宽高的弹窗在视口居中?(Flex / Grid / fixed + transform)
place-items: center是哪两个属性的简写?
2. 什么是 BFC?如何触发?解决了什么问题? ⭐⭐
解释 BFC(Block Formatting Context)的概念、触发方式和实际应用。
考察点:BFC 规则、外边距折叠、浮动清除
BFC 是什么
BFC(块级格式化上下文)是页面中的一块独立渲染区域,内部元素的布局不会影响外部,外部也不会影响内部。
触发 BFC 的方式
css
/* 任意一种即可触发 BFC */
overflow: hidden | auto | scroll; /* 不为 visible */
display: flow-root; /* 最语义化,推荐 */
display: flex | inline-flex;
display: grid | inline-grid;
display: table-cell | table-caption;
float: left | right; /* 不为 none */
position: absolute | fixed; /* 脱离文档流 */
contain: layout | content | paint;
column-count / column-width; /* 多列布局 */BFC 能解决的三大问题
① 外边距折叠(Margin Collapsing)
html
<!-- 问题:两个相邻块的 margin 会合并 -->
<div style="margin-bottom: 20px">A</div>
<div style="margin-top: 30px">B</div>
<!-- 实际间距是 30px 不是 50px -->
<!-- 解法:让其中一个在独立的 BFC 中 -->
<div style="margin-bottom: 20px">A</div>
<div style="display: flow-root">
<div style="margin-top: 30px">B</div>
</div>
<!-- 间距 = 20 + 30 = 50px -->② 浮动元素导致父容器高度塌陷
css
/* 问题:子元素全部浮动,父容器高度为 0 */
.parent {
/* 解法:触发 BFC */
display: flow-root; /* 推荐 */
/* 或 overflow: hidden; */
}
.child {
float: left;
}③ 浮动元素与文本的环绕问题
css
/* 让侧边栏和主内容互不干扰 */
.sidebar {
float: left;
width: 200px;
}
.main {
overflow: hidden; /* 触发 BFC,不被 sidebar 覆盖 */
}display: flow-root vs overflow: hidden
| 特性 | flow-root | overflow: hidden |
|---|---|---|
| 语义 | ✅ 专门用于创建 BFC | ❌ 本意是裁剪溢出内容 |
| 副作用 | 无 | 截断 box-shadow、dropdown 等溢出内容 |
| 兼容性 | Chrome 58+ | IE8+ |
追问延伸
- IFC(Inline Formatting Context)和 BFC 的区别?
- 为什么
display: flex的子元素不会发生外边距折叠? overflow: hidden创建 BFC 是"特性"还是"规范行为"?
3. position 的 5 个值及其区别?sticky 的触发条件? ⭐
说明 CSS 定位的 5 个值,重点阐述
sticky的工作原理。
考察点:定位模型、包含块、sticky 边界
5 种定位值
| 值 | 是否脱离文档流 | 参照物 | 特点 |
|---|---|---|---|
static | ❌ | — | 默认值,top/left 等无效 |
relative | ❌ | 自身原始位置 | 偏移后原始空间仍保留 |
absolute | ✅ | 最近的非 static 祖先 | 完全脱离,不占空间 |
fixed | ✅ | 视口(viewport) | 滚动不动,但 transform 祖先会改变参照物 |
sticky | ❌(切换前) | 最近可滚动祖先 | 在 relative 和 fixed 间切换 |
sticky 深入
css
.sticky-header {
position: sticky;
top: 0;
}sticky 的生效条件(全部满足才有效):
- 必须指定
top/bottom/left/right中至少一个 - 父容器的高度必须大于 sticky 元素本身(否则没有滚动空间)
- 父容器不能有
overflow: hidden | auto | scroll(否则不是视口触发滚动) - sticky 元素在其包含块内活动,滚出包含块后"失效"
sticky 工作过程:
┌─── 包含块(父容器)───┐
│ │
│ ┌─ sticky 元素 ─┐ │ ← 正常流中跟随滚动(相当于 relative)
│ └───────────────┘ │
│ │ ← 滚到 top: 0 时"粘住"(相当于 fixed)
│ │
└───────────────────────┘ ← 滚出包含块时,sticky 元素被"带走"fixed 的"陷阱"
css
/* 当祖先有 transform 时,fixed 不再相对于视口! */
.ancestor {
transform: translateZ(0); /* 或任何非 none 的 transform */
}
.child {
position: fixed; /* 此时相对于 .ancestor 定位,而非视口 */
}追问延伸
position: sticky能否同时指定top和bottom?(可以,用于"弹性粘性")- 为什么
transform会改变fixed的参照物?(创建了新的包含块) - 如何用 JS 判断 sticky 是否已经"粘住"了?(IntersectionObserver)
4. Flex 中 flex: 1 到底等于什么?flex-basis 和 width 的优先级? ⭐⭐
深入解析 Flex 三个子属性的计算规则。
考察点:Flex 简写展开、flex-grow/shrink/basis 计算
flex 简写展开
| 简写 | flex-grow | flex-shrink | flex-basis |
|---|---|---|---|
flex: 1 | 1 | 1 | 0% |
flex: auto | 1 | 1 | auto |
flex: none | 0 | 0 | auto |
flex: 0 auto | 0 | 1 | auto(= initial) |
flex: 2 1 100px | 2 | 1 | 100px |
⚠️
flex: 1≠flex-grow: 1。flex: 1会将flex-basis设为0%,而单独写flex-grow: 1时flex-basis保持默认的auto。
flex-basis vs width 优先级
优先级链:
flex-basis (非 auto) > width > 内容宽度
↑
flex-basis: auto 时退回到 widthcss
.item {
width: 200px;
flex-basis: 100px; /* 实际使用 100px 作为初始主轴尺寸 */
}
.item2 {
width: 200px;
flex-basis: auto; /* 退回到 width: 200px */
}
.item3 {
/* 无 width,无 flex-basis */
/* 使用内容宽度 (min-content) */
}flex-grow 分配计算
容器宽度: 600px
子元素 A: flex: 1, 内容 100px → basis = 0%
子元素 B: flex: 2, 内容 200px → basis = 0%
子元素 C: flex: 0 0 100px → basis = 100px
剩余空间 = 600 - 0 - 0 - 100 = 500px
A 分得: 500 × (1/3) ≈ 166.7px
B 分得: 500 × (2/3) ≈ 333.3px
C 分得: 0(grow = 0)
最终: A=166.7px, B=333.3px, C=100pxflex-shrink 压缩计算
容器宽度: 300px
子元素 A: flex: 0 1 200px
子元素 B: flex: 0 2 200px
溢出 = 200 + 200 - 300 = 100px
加权总和 = 1×200 + 2×200 = 600
A 压缩: 100 × (1×200/600) = 33.3px → 最终 166.7px
B 压缩: 100 × (2×200/600) = 66.7px → 最终 133.3px
注意: shrink 是按 shrink × basis 的加权比例压缩!追问延伸
min-width: 0在 Flex 布局中有什么用?(解决文本溢出不压缩的问题)flex-basis: 0%和flex-basis: 0px有区别吗?- 为什么
flex: 1的元素设了width却不生效?
5. 说说层叠上下文和 z-index 的规则 ⭐⭐
解释层叠上下文的创建条件和
z-index的比较规则。
考察点:层叠顺序、层叠上下文创建、z-index 失效
层叠顺序(从低到高)
最底层
↓ 层叠上下文背景/边框
↓ z-index < 0 的定位元素
↓ 块级元素(普通流)
↓ 浮动元素
↓ 行内/行内块元素
↓ z-index: 0 / auto 的定位元素
↓ z-index > 0 的定位元素
最顶层创建层叠上下文的条件
css
/* 以下任意一种都会创建新的层叠上下文 */
position: relative/absolute/fixed + z-index 不为 auto
opacity < 1
transform 不为 none
filter 不为 none
will-change: transform / opacity / filter
isolation: isolate
mix-blend-mode 不为 normal
-webkit-overflow-scrolling: touch
contain: layout / paintz-index 的"失效"场景
html
<!-- 经典问题:为什么 z-index: 9999 还是被遮挡? -->
<div class="parent-a" style="position: relative; z-index: 1;">
<div class="child" style="position: absolute; z-index: 9999;">
我 z-index 很大,但...
</div>
</div>
<div class="parent-b" style="position: relative; z-index: 2;">
我遮住了 child!
</div>
<!--
原因: child 的 z-index 只在 parent-a 的层叠上下文内比较
parent-a (z:1) vs parent-b (z:2) → parent-b 在上
child 的 9999 和 parent-b 不在同一个上下文中,无法比较
-->层叠上下文树:
root (z: auto)
├── parent-a (z: 1) ← 创建了层叠上下文
│ └── child (z: 9999) ← 只在 parent-a 内部排序
└── parent-b (z: 2) ← 和 parent-a 同级比较
parent-b 赢(2 > 1)最佳实践
css
/* 用 isolation 创建干净的层叠上下文,不需要 z-index */
.modal-container {
isolation: isolate; /* 创建新的层叠上下文,内部 z-index 不外泄 */
}
.modal-backdrop { z-index: 1; }
.modal-content { z-index: 2; }
/* 这些 z-index 只在 .modal-container 内部生效 */追问延伸
opacity: 0.99会创建新的层叠上下文,有什么实际影响?isolation: isolate的使用场景?- 为什么
position: fixed元素有时会被transform容器的子元素遮挡?
6. CSS 选择器优先级如何计算? ⭐
说明 CSS 特异性(Specificity)的计算规则和
!important的最佳实践。
考察点:特异性计算、层叠规则
优先级计算
特异性表示为 (a, b, c) 的权重向量:
a = ID 选择器的数量
b = 类选择器 / 属性选择器 / 伪类的数量
c = 元素选择器 / 伪元素的数量
比较规则: 从左到右逐级比较,大的赢| 选择器 | (a, b, c) | 说明 |
|---|---|---|
* | (0, 0, 0) | 通配符不计数 |
div | (0, 0, 1) | 1 个元素 |
.cls | (0, 1, 0) | 1 个类 |
#id | (1, 0, 0) | 1 个 ID |
div.cls | (0, 1, 1) | 1 类 + 1 元素 |
#id .cls div | (1, 1, 1) | 1 ID + 1 类 + 1 元素 |
div div div div div | (0, 0, 5) | 5 个元素,但仍 < 1 个类 |
:is(#id) | (1, 0, 0) | :is() 取参数中最高特异性 |
:where(#id) | (0, 0, 0) | :where() 特异性始终为 0 |
style="" | 内联样式 | 高于任何选择器 |
!important | — | 高于内联,进入 important 层级比较 |
层叠优先级全貌
1. 用户代理 !important
2. 用户 !important
3. 作者 !important ← 你写的 !important
4. @layer 中的 !important (注意: layer 中 important 优先级反转)
5. 作者普通样式
6. @layer 中的普通样式
7. 用户普通样式
8. 用户代理普通样式!important 最佳实践
css
/* ❌ 滥用 !important */
.header { color: red !important; }
.header.active { color: blue !important; } /* 不得不继续加 !important */
/* ✅ 合理使用场景 */
/* 1. 覆盖第三方库样式 */
.ant-btn-primary {
background-color: #6366f1 !important;
}
/* 2. 工具类(Tailwind 式) */
.hidden { display: none !important; }
.sr-only { position: absolute !important; /* ... */ }
/* ✅ 更好的替代方案 */
/* 用 :where() 降低第三方选择器的特异性 */
:where(.third-party) .my-component {
color: blue; /* 不需要 !important 也能覆盖 */
}追问延伸
:is()和:where()的特异性区别?实际中怎么利用?- CSS
@layer如何管理第三方样式的优先级? - 11 个类选择器能否胜过 1 个 ID 选择器?(不能,不同维度不进位)
7. transform 动画为什么比 left/top 性能好? ⭐⭐⭐
解释浏览器渲染流水线和合成层(Compositing Layer)。
考察点:渲染流水线、GPU 加速、合成层
浏览器渲染流水线
JS / CSS → Style → Layout → Paint → Composite
↑ ↑ ↑
回流 重绘 合成
(Reflow) (Repaint) (Composite)| 属性变化 | 触发阶段 | 性能开销 |
|---|---|---|
width / height / left / top | Layout → Paint → Composite | 🔴 高(回流) |
color / background / box-shadow | Paint → Composite | 🟡 中(重绘) |
transform / opacity | 仅 Composite | 🟢 低(合成) |
为什么 transform 快?
left/top 动画:
每一帧 → 重新计算布局 → 重新绘制整个图层 → 合成
↑ 主线程阻塞 ↑ GPU
transform 动画:
第一帧 → 提升到独立合成层(GPU 纹理)
后续帧 → GPU 直接变换纹理坐标 ← 主线程空闲!合成层(Compositing Layer)
css
/* 以下属性会让元素提升到独立的合成层 */
transform: translateZ(0); /* hack 触发 */
will-change: transform; /* 规范方式 */
opacity 动画中;
position: fixed;
/* 等等 */合成层的"层爆炸"问题
css
/* ❌ 不要给大量元素加 will-change */
.list-item {
will-change: transform; /* 1000 个列表项 → 1000 个 GPU 纹理 → 显存爆炸 */
}
/* ✅ 按需触发 */
.card {
transition: transform 0.3s;
}
.card:hover {
will-change: transform; /* hover 时才提升 */
}
/* 或用 JS 在动画开始前加,动画结束后移除 */动画性能对比
css
/* ❌ 性能差 */
.box {
position: absolute;
transition: left 0.3s, top 0.3s;
}
.box:hover {
left: 100px;
top: 50px;
}
/* ✅ 性能好 */
.box {
transition: transform 0.3s;
}
.box:hover {
transform: translate(100px, 50px);
}追问延伸
- 如何用 Chrome DevTools 查看合成层?(Layers 面板)
will-change什么时候该用,什么时候不该用?- CSS
contain: strict和合成层有什么关系?
8. rem/em/vw/vh 的区别?移动端适配方案对比 ⭐⭐
对比 CSS 长度单位,并梳理主流移动端适配方案。
考察点:响应式单位、移动端适配
单位对比
| 单位 | 参照物 | 特点 |
|---|---|---|
px | 设备独立像素(CSS 像素) | 绝对值,不响应字体/视口变化 |
em | 父元素的 font-size | 嵌套时会复合放大(坑) |
rem | 根元素 <html> 的 font-size | 全局统一基准 |
vw | 视口宽度的 1% | 100vw = 视口宽度 |
vh | 视口高度的 1% | 移动端有地址栏问题 |
dvh | 动态视口高度 | 排除地址栏,推荐 |
svh / lvh | 最小/最大视口高度 | 针对移动端浏览器 |
cqw / cqh | 容器查询单位 | 相对于最近 container 祖先 |
em 的"坑"
css
.parent { font-size: 16px; }
.child { font-size: 1.5em; } /* 16 × 1.5 = 24px */
.grandchild { font-size: 1.5em; } /* 24 × 1.5 = 36px!不是 24px */
/* em 层层复合,难以预测 */移动端适配方案
① rem + 动态 html font-size(淘宝 flexible 方案,已过时)
javascript
document.documentElement.style.fontSize =
document.documentElement.clientWidth / 10 + 'px'
// 375px 设备 → html font-size = 37.5px
// 设计稿 100px → 100 / 37.5 ≈ 2.667rem② viewport + vw(当前主流)
css
/* 设计稿 750px,1px = 100/750 vw */
.box {
width: 13.333vw; /* = 100px / 750 × 100 */
font-size: 4.267vw; /* = 32px / 750 × 100 */
}
/* 配合 postcss-px-to-viewport 自动转换 */
/* postcss.config.js */
/* { viewportWidth: 375 } → 直接写 px,编译时自动转 vw */③ 响应式 + CSS 变量 + clamp()(现代方案)
css
:root {
--font-base: clamp(14px, 2vw + 8px, 18px);
--spacing: clamp(8px, 1.5vw, 16px);
}
body {
font-size: var(--font-base);
}| 方案 | 优点 | 缺点 |
|---|---|---|
| rem + JS | 兼容性好 | 需要 JS、闪屏 |
| vw | 纯 CSS、线性缩放 | 大屏不封顶、文字可能过大 |
| clamp() | 有上下限控制 | 计算稍复杂 |
追问延伸
- 移动端
vh为什么不准?dvh怎么解决的? postcss-px-to-viewport和postcss-pxtorem各自原理?- 如何处理 1px 问题?(下一题详解)
9. 如何实现 1px 细线?为什么会有 1px 问题? ⭐⭐
解释设备像素比导致的 1px 问题,并给出解决方案。
考察点:DPR(设备像素比)、物理像素、CSS 像素
为什么有 1px 问题
CSS 1px ≠ 物理 1px
在 DPR = 2 的设备上(如 iPhone):
CSS 1px → 实际渲染 2 个物理像素 → 线条看起来"粗"
设计稿期望的 1px 是物理像素的 1px
但 CSS 的 1px 是设备独立像素解决方案
① transform scale(最常用)
css
.border-1px {
position: relative;
}
.border-1px::after {
content: '';
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 1px;
background: #000;
transform: scaleY(0.5); /* DPR=2 时缩放到 0.5 */
transform-origin: 0 0;
}
/* 四边边框 */
.border-1px-all::after {
content: '';
position: absolute;
inset: 0;
border: 1px solid #000;
transform: scale(0.5);
transform-origin: 0 0;
width: 200%;
height: 200%;
pointer-events: none;
}② viewport meta + rem(整体缩放)
javascript
const dpr = window.devicePixelRatio
const scale = 1 / dpr
document.querySelector('meta[name="viewport"]').setAttribute(
'content',
`width=device-width,initial-scale=${scale},user-scalable=no`
)
// DPR=2 → initial-scale=0.5 → CSS 1px = 物理 1px
// 但所有尺寸都要乘以 dpr 来补偿③ border-image / SVG
css
.border-1px {
border-bottom: 1px solid transparent;
border-image: url("data:image/svg+xml,...") 2 stretch;
}④ 0.5px(现代方案)
css
/* iOS 8+ 和现代 Chrome 支持 */
.border-hairline {
border: 0.5px solid #000;
}
/* 渐进增强 */
@media (-webkit-min-device-pixel-ratio: 2) {
.border-1px {
border-width: 0.5px;
}
}| 方案 | 兼容性 | 复杂度 | 推荐度 |
|---|---|---|---|
| transform scale | 全兼容 | 中 | ⭐⭐⭐ |
| viewport 缩放 | 全兼容 | 高(影响全局) | ⭐⭐ |
| 0.5px | iOS 8+, Chrome 56+ | 低 | ⭐⭐⭐(现代项目) |
| border-image | IE11+ | 中 | ⭐ |
追问延伸
window.devicePixelRatio返回什么?DPR=3 的设备有哪些?- Retina 屏下图片模糊怎么处理?(2x/3x 图、
srcset) image-rendering: pixelated什么时候用?
10. CSS 变量 vs Sass 变量?运行时 vs 编译时? ⭐⭐
对比 CSS 自定义属性和预处理器变量的本质区别。
考察点:CSS 自定义属性、级联继承、运行时动态
核心区别
| 维度 | CSS 变量 (--var) | Sass 变量 ($var) |
|---|---|---|
| 生效时机 | 运行时 | 编译时 |
| 级联/继承 | ✅ 参与 CSS 级联 | ❌ 编译后消失 |
| JS 交互 | ✅ 可读写 | ❌ |
| 作用域 | 选择器级别(DOM 树) | 块级作用域({} 内) |
| 默认值 | var(--color, red) | $color: red !default |
| 计算能力 | 有限(calc() 内) | 强大(循环、条件、函数) |
| 媒体查询内修改 | ✅ | ❌ |
CSS 变量的级联能力
css
:root {
--primary: #6366f1;
--spacing: 16px;
}
.dark-theme {
--primary: #818cf8; /* 暗色主题自动覆盖 */
}
.card {
background: var(--primary);
padding: var(--spacing);
}
/* 媒体查询中修改 */
@media (max-width: 768px) {
:root {
--spacing: 8px; /* 移动端间距缩小,所有用到 --spacing 的地方自动更新 */
}
}JS 操作 CSS 变量
javascript
const root = document.documentElement
root.style.setProperty('--primary', '#06b6d4')
const value = getComputedStyle(root).getPropertyValue('--primary')
root.style.setProperty('--mouse-x', `${e.clientX}px`)
root.style.setProperty('--mouse-y', `${e.clientY}px`)主题切换实践
css
/* 定义主题变量 */
:root {
--bg: #ffffff;
--text: #1a1a1a;
--primary: #6366f1;
}
:root[data-theme="dark"] {
--bg: #0a0a0a;
--text: #ededed;
--primary: #818cf8;
}
/* 使用变量 */
body {
background: var(--bg);
color: var(--text);
}javascript
function toggleTheme() {
const isDark = document.documentElement.dataset.theme === 'dark'
document.documentElement.dataset.theme = isDark ? 'light' : 'dark'
}CSS 变量 + Sass 最佳组合
scss
// _variables.scss — 编译时计算
$breakpoints: (sm: 640px, md: 768px, lg: 1024px);
$colors: (primary: #6366f1, secondary: #06b6d4);
// 编译时生成 CSS 变量
:root {
@each $name, $color in $colors {
--color-#{$name}: #{$color};
}
}
// 运行时使用 CSS 变量
.button {
background: var(--color-primary);
}追问延伸
- CSS 变量能否用于媒体查询的条件?(不能,
@media (min-width: var(--bp))无效) @property注册自定义属性有什么用?(类型、继承、初始值,可做渐变动画)- PostCSS 的
postcss-custom-properties插件做了什么?
11. @layer 是什么?解决了什么问题? ⭐⭐
解释 CSS 级联层(Cascade Layers)的工作原理和实际应用。
考察点:CSS 级联层、样式优先级管理
为什么需要 @layer
传统 CSS 优先级管理的痛点:
1. 引入第三方库(如 Ant Design)→ 选择器特异性高
2. 你的样式想覆盖它 → 不得不写更高特异性或 !important
3. 引入另一个库 → !important 大战开始 💥
@layer 的解决方案: 把样式分层,层之间的优先级 > 选择器特异性基本用法
css
/* ① 声明层顺序(越后面优先级越高) */
@layer reset, base, components, utilities;
/* ② 把样式放入对应层 */
@layer reset {
* { margin: 0; padding: 0; box-sizing: border-box; }
}
@layer base {
body { font-family: system-ui; line-height: 1.5; }
a { color: var(--primary); }
}
@layer components {
.btn { padding: 8px 16px; border-radius: 4px; }
.card { border: 1px solid #e5e7eb; }
}
@layer utilities {
.hidden { display: none; }
.text-center { text-align: center; }
}层优先级规则
① 未分层的样式 > 任何层内的样式
② 层按声明顺序排列,后声明的优先级高
③ 层内部按正常的特异性规则比较
@layer reset, base, components, utilities;
优先级排序(从低到高):
reset < base < components < utilities < 未分层样式实战:管理第三方库
css
/* 把第三方样式放入低优先级层 */
@layer third-party, custom;
@import url('antd.css') layer(third-party);
@layer custom {
/* 你的样式,不需要高特异性或 !important 就能覆盖 antd */
.ant-btn {
border-radius: 8px;
}
}@layer 中 !important 的反转
css
@layer A, B;
/* 正常样式: B > A(后声明的赢)*/
/* !important: A > B(先声明的赢!完全反转) */
@layer A {
.box { color: red !important; } /* ← 这个赢 */
}
@layer B {
.box { color: blue !important; }
}
/* .box 的 color 是 red */追问延伸
@layer和@scope的区别?各自解决什么问题?- Tailwind CSS v3.4+ 是如何利用
@layer的? - 匿名层(
@layer { ... }不写名字)的优先级如何?
12. contain 属性是什么?如何用于性能优化? ⭐⭐⭐
解释 CSS Containment 的工作原理和性能优化策略。
考察点:CSS 容器限制、渲染优化、内容可见性
contain 是什么
contain 告诉浏览器:这个元素的内部变化不会影响外部,从而让浏览器跳过不必要的计算。
各值含义
| 值 | 限制 | 效果 |
|---|---|---|
layout | 内部布局不影响外部 | 内部元素改变不触发外部回流 |
paint | 内部绘制不溢出 | 类似 overflow: hidden,不渲染可视区域外的内容 |
size | 元素的大小不依赖子元素 | 必须显式设置宽高,子元素变化不影响父元素尺寸 |
style | 计数器、quotes 等不外泄 | 较少使用 |
strict | = size layout paint | 最强限制 |
content | = layout paint + style | 推荐:适用于大多数场景 |
实战:长列表优化
css
/* 每个列表项独立包含,互不影响 */
.list-item {
contain: content;
/* 浏览器知道: 改变某个 item 的内容不需要重算整个列表 */
}content-visibility(更强力的优化)
css
.card {
content-visibility: auto;
contain-intrinsic-size: auto 300px;
}content-visibility: auto 的效果:
┌────────────── 视口 ──────────────┐
│ ┌─ card 1 ─┐ 正常渲染 │
│ └──────────┘ │
│ ┌─ card 2 ─┐ 正常渲染 │
│ └──────────┘ │
└──────────────────────────────────┘
┌─ card 3 ─┐ 跳过渲染! ← 屏幕外
└──────────┘ 但保留占位高度
┌─ card 4 ─┐ 跳过渲染!
└──────────┘
contain-intrinsic-size: auto 300px
→ 告诉浏览器"在我没渲染时,假设我高 300px"
→ 防止滚动条跳动
→ auto 关键字:渲染过一次后记住真实高度性能收益
某电商列表页实测(500+ 商品卡片):
不加 contain:
首次渲染: ~800ms
滚动帧率: ~45fps
加 content-visibility: auto:
首次渲染: ~200ms(⬇ 75%)
滚动帧率: ~60fps追问延伸
content-visibility: auto和虚拟列表(Virtual Scroll)的区别?各自适用场景?contain-intrinsic-size: auto 300px中的auto有什么用?contain: strict什么时候会出问题?(忘记设宽高 → 元素塌陷为 0)
13. CSS Grid 和 Flex 各自适用什么场景? ⭐
对比 Grid 和 Flex 的设计理念和最佳使用场景。
考察点:布局模型选择
核心区别
| 维度 | Flexbox | Grid |
|---|---|---|
| 维度 | 一维(主轴方向) | 二维(行 + 列) |
| 布局方向 | 内容驱动(Content-first) | 容器驱动(Layout-first) |
| 对齐 | 沿主轴 / 交叉轴 | 行列交叉的单元格 |
| 适合 | 组件内部排列 | 页面整体布局 |
Flex 最佳场景
css
/* ① 导航栏 / 工具栏 */
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
}
/* ② 水平/垂直居中 */
.center {
display: flex;
justify-content: center;
align-items: center;
}
/* ③ 等分容器 */
.tabs {
display: flex;
}
.tab {
flex: 1;
}
/* ④ 自适应布局(一个固定 + 一个撑满) */
.sidebar-layout {
display: flex;
}
.sidebar { width: 240px; flex-shrink: 0; }
.main { flex: 1; }Grid 最佳场景
css
/* ① 经典页面布局 */
.page {
display: grid;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
grid-template-columns: 240px 1fr;
grid-template-rows: auto 1fr auto;
min-height: 100vh;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }
/* ② 卡片网格(自动填充) */
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 24px;
}
/* ③ 不规则布局(跨行跨列) */
.dashboard {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
}
.widget-large {
grid-column: span 2;
grid-row: span 2;
}选择决策树
需要排列一组元素?
├── 只需要一个方向(水平或垂直)?
│ └── ✅ Flexbox
├── 需要行和列同时控制?
│ └── ✅ Grid
├── 元素数量不确定,需要自动换行?
│ ├── 每行/列的元素数量重要? → Grid (auto-fill)
│ └── 只需要自然流动? → Flex (flex-wrap)
└── 经典圣杯/双飞翼布局?
└── ✅ Grid (grid-template-areas)可以组合使用
css
/* Grid 做页面级布局,Flex 做组件级布局 */
.page {
display: grid;
grid-template-columns: 240px 1fr;
}
.toolbar {
display: flex;
gap: 8px;
align-items: center;
}追问延伸
repeat(auto-fill, ...)和repeat(auto-fit, ...)的区别?- Grid 的
subgrid是什么?解决了什么问题? - 为什么说 Flex 是"内容驱动",Grid 是"布局驱动"?
14. :has() 选择器能做什么? ⭐⭐
给出
:has()选择器的 3 个以上实际用例。
考察点:CSS 新特性、"父选择器"
:has() 是什么
:has() 是 CSS 中期盼已久的关系选择器(常被称为"父选择器"),它可以根据后代/兄弟的状态来选择祖先或兄弟元素。
css
/* 选中"包含 img 的" .card */
.card:has(img) {
grid-column: span 2;
}
/* 选中"后面紧跟 p 的" h2 */
h2:has(+ p) {
margin-bottom: 8px;
}实际用例
① 表单验证状态
css
/* 包含无效 input 的 label 标红 */
.form-group:has(input:invalid) {
border-color: red;
}
.form-group:has(input:invalid) label {
color: red;
}
/* 包含 focus 的 input 的表单组高亮 */
.form-group:has(input:focus) {
box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.3);
}② 条件布局
css
/* 有图片的卡片用水平布局,没图片的用垂直布局 */
.card:has(img) {
display: grid;
grid-template-columns: 200px 1fr;
}
.card:not(:has(img)) {
display: flex;
flex-direction: column;
}③ 全局主题响应
css
/* 当页面中有打开的 modal 时,禁止 body 滚动 */
body:has(.modal.open) {
overflow: hidden;
}
/* 当 sidebar 展开时,main 区域缩窄 */
.layout:has(.sidebar.expanded) .main {
margin-left: 240px;
}④ 空状态处理
css
/* 列表为空时显示提示 */
.list:not(:has(.list-item)) {
display: grid;
place-items: center;
min-height: 200px;
}
.list:not(:has(.list-item))::after {
content: "暂无数据";
color: #9ca3af;
}⑤ 星级评分(纯 CSS)
css
/* 鼠标悬停在某颗星上时,它和前面的星都高亮 */
.rating:has(input:nth-of-type(3):hover) label:nth-of-type(-n+3) {
color: gold;
}兼容性与注意事项
| 浏览器 | 支持版本 |
|---|---|
| Chrome | 105+ |
| Safari | 15.4+ |
| Firefox | 121+ |
| Edge | 105+ |
css
/* 渐进增强写法 */
@supports selector(:has(*)) {
.card:has(img) { /* 现代浏览器 */ }
}追问延伸
:has()的性能怎样?浏览器是如何优化的?:has()能否替代一些 JavaScript 的 DOM 判断逻辑?:has(:is(.a, .b))和:has(.a, .b)有区别吗?
15. 如何实现宽高比固定的容器? ⭐⭐
对比
aspect-ratio属性和传统padding-tophack。
考察点:padding-top hack、aspect-ratio、响应式容器
现代方案:aspect-ratio
css
/* 16:9 视频容器 */
.video-container {
width: 100%;
aspect-ratio: 16 / 9;
background: #000;
}
/* 正方形头像 */
.avatar {
width: 80px;
aspect-ratio: 1;
border-radius: 50%;
object-fit: cover;
}
/* 配合 min/max 限制 */
.card-image {
width: 100%;
aspect-ratio: 4 / 3;
max-height: 400px; /* aspect-ratio 会在 max-height 处被截断 */
object-fit: cover;
}传统方案:padding-top hack
css
/*
原理: padding 的百分比是相对于 **父元素宽度** 计算的
所以 padding-top: 56.25% = 9/16 × 100%
*/
.video-wrapper {
position: relative;
width: 100%;
padding-top: 56.25%; /* 16:9 */
height: 0;
}
.video-wrapper > * {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}对比
| 维度 | aspect-ratio | padding-top hack |
|---|---|---|
| 代码量 | 1 行 | 5+ 行 |
| 直观性 | ✅ 16 / 9 一目了然 | ❌ 56.25% 需要计算 |
| 子元素 | 正常排列 | 必须绝对定位 |
| 响应内容 | 内容可以撑开容器 | 内容不影响容器高度 |
| 兼容性 | Chrome 88+, Safari 15+ | IE8+ |
aspect-ratio 的交互行为
css
.box {
width: 300px;
aspect-ratio: 2 / 1;
/* 默认 height = 150px */
}
/* 当显式设置 height 时,aspect-ratio 失效 */
.box {
width: 300px;
height: 200px;
aspect-ratio: 2 / 1; /* 被忽略,height 优先 */
}
/* 但 min/max-height 可以和 aspect-ratio 配合 */
.box {
width: 100%;
aspect-ratio: 16 / 9;
max-height: 500px; /* 宽度很大时,高度被限制在 500px */
}常见宽高比速查
| 比例 | aspect-ratio | padding-top | 应用场景 |
|---|---|---|---|
| 16:9 | 16 / 9 | 56.25% | 视频、Banner |
| 4:3 | 4 / 3 | 75% | 传统图片 |
| 1:1 | 1 | 100% | 头像、缩略图 |
| 3:2 | 3 / 2 | 66.67% | 相册 |
| 21:9 | 21 / 9 | 42.86% | 超宽屏 |
追问延伸
aspect-ratio和object-fit的区别?分别作用于什么?- 在 Flex / Grid 子项上使用
aspect-ratio需要注意什么?(可能被 stretch 覆盖) - 如何用
@container查询实现响应式宽高比切换?