CSS 高频面试题
1. 盒模型是什么?标准盒模型和 IE 盒模型的区别?
盒模型(Box Model) 是 CSS 中最基础的概念之一。每个 HTML 元素都可以看作一个矩形的盒子,由 4 层组成:content(内容)→ padding(内边距)→ border(边框)→ margin(外边距)。
两种盒模型的核心区别在于 width 属性包含的范围不同:
| 盒模型 | box-sizing 值 | width 包含 | 实际占用宽度 |
|---|---|---|---|
| 标准盒模型(W3C) | content-box(默认) | 仅 content | width + padding×2 + border×2 |
| IE 盒模型(怪异盒模型) | border-box | content + padding + border | 就是 width |
/* ✅ 标准盒模型:width = content */
.standard {
box-sizing: content-box; /* 默认值 */
width: 200px;
padding: 20px;
border: 5px solid #000;
/* 实际占用宽度:200 + 20×2 + 5×2 = 250px */
/* content 区域宽度 = 200px */
}
/* ✅ IE 盒模型:width = content + padding + border */
.ie-box {
box-sizing: border-box;
width: 200px;
padding: 20px;
border: 5px solid #000;
/* 实际占用宽度:200px */
/* content 区域宽度 = 200 - 20×2 - 5×2 = 150px */
}
/* ✅ 推荐全局设置为 border-box(现代项目标配) */
*, *::before, *::after {
box-sizing: border-box;
}
为什么推荐 border-box?
- 设置
width: 200px就是最终宽度 200px,不用再做加法 - 修改
padding和border不会改变元素的最终尺寸,布局更可控 - Bootstrap、Tailwind 等主流框架都默认使用
border-box
💡 面试加分点:
margin不属于任何一种盒模型的width计算范围,但会影响元素的"占位空间"。可以通过getComputedStyle(el)获取元素的计算样式,或通过el.offsetWidth(包含 padding + border)和el.clientWidth(包含 padding,不含 border)来获取元素的实际宽度。
2. BFC(块级格式化上下文)是什么?如何触发?
BFC(Block Formatting Context) 是一个独立的渲染区域。BFC 内部的布局与外部完全隔离,互不影响。理解 BFC 是解决很多 CSS 布局"怪问题"的关键。
BFC 的核心特性:
- 内部的块级元素从上到下依次排列
- 同一个 BFC 内的相邻块级元素会发生 margin 折叠
- BFC 的区域不会与浮动元素重叠
- BFC 是一个隔离容器,内部元素不影响外部
- 计算 BFC 的高度时,浮动子元素也参与计算(关键!)
触发 BFC 的方式(任一即可):
| 属性 | 值 | 副作用 |
|---|---|---|
overflow | hidden / auto / scroll | 可能裁剪内容 |
display | flex / grid / inline-block / table-cell / flow-root | 改变布局模式 |
position | absolute / fixed | 脱离文档流 |
float | left / right | 脱离文档流 |
display | flow-root(推荐 ✅) | 无副作用 |
/* ✅ 场景1:清除浮动(解决父元素高度塌陷) */
/* 问题:父元素没有高度,因为浮动子元素脱离了文档流 */
.parent {
overflow: hidden; /* 触发 BFC,使浮动子元素参与高度计算 */
}
/* 或者用更语义化的方式(推荐) */
.parent {
display: flow-root; /* 专门为触发 BFC 设计,无副作用 */
}
.child { float: left; }
/* ✅ 场景2:防止 margin 折叠 */
/* 问题:两个相邻 div 的 margin-top 和 margin-bottom 会合并 */
.box1 { margin-bottom: 20px; }
.box2 { margin-top: 30px; }
/* 结果:两者间距是 30px 而不是 50px(取较大值) */
/* 解决:让其中一个在新的 BFC 中 */
.box2-wrapper { overflow: hidden; } /* 包一层,触发新 BFC */
/* ✅ 场景3:自适应两栏布局 */
.left { float: left; width: 200px; background: #f0f0f0; }
.right { overflow: hidden; background: #e0e0e0; }
/* right 触发 BFC 后,不会与左侧浮动元素重叠,自动填充剩余空间 */
💡 面试加分点:
display: flow-root是 CSS 中专门为创建 BFC 而设计的属性值,没有任何布局副作用,是目前最推荐的触发 BFC 方式。此外,BFC 只解决块级方向(垂直方向)的 margin 折叠,行内方向的 margin 不会折叠。
3. Flex 布局常用属性有哪些?
Flexbox(弹性盒子布局) 是一维布局模型,主要用于在一个方向(水平或垂直)上排列元素。它极大简化了对齐、分布和排序的复杂度。
核心概念: 容器(flex container)和子项(flex item),以及两根轴:主轴(main axis) 和 交叉轴(cross axis)。
/* ========== 容器属性 ========== */
.container {
display: flex;
/* 主轴方向 */
flex-direction: row; /* 默认:水平从左到右 */
/* row | row-reverse | column | column-reverse */
/* 是否换行 */
flex-wrap: nowrap; /* 默认:不换行 */
/* nowrap | wrap | wrap-reverse */
/* 主轴对齐(分配剩余空间) */
justify-content: flex-start; /* 默认:靠主轴起点 */
/* flex-start | flex-end | center | space-between | space-around | space-evenly */
/* 交叉轴对齐(单行) */
align-items: stretch; /* 默认:拉伸填满容器高度 */
/* stretch | flex-start | flex-end | center | baseline */
/* 交叉轴对齐(多行,需配合 flex-wrap: wrap) */
align-content: flex-start;
/* flex-start | flex-end | center | space-between | space-around | stretch */
/* 间距(推荐,替代 margin 方案) */
gap: 10px 20px; /* 行间距 列间距 */
}
/* ========== 子项属性 ========== */
.item {
flex: 1; /* 简写:flex-grow flex-shrink flex-basis */
/* flex: 1 等价于 flex: 1 1 0% */
flex-grow: 1; /* 放大比例,默认 0(不放大) */
flex-shrink: 1; /* 缩小比例,默认 1(等比缩小) */
flex-basis: auto; /* 初始大小,默认 auto(使用元素自身宽/高) */
align-self: auto; /* 覆盖容器的 align-items */
order: 0; /* 排列顺序,数值越小越靠前 */
}
flex 简写的常用值:
| 简写 | 等价于 | 含义 |
|---|---|---|
flex: 1 | flex: 1 1 0% | 等比例分配剩余空间 |
flex: auto | flex: 1 1 auto | 按内容大小分配 |
flex: none | flex: 0 0 auto | 不伸不缩,保持原始大小 |
flex: 0 1 auto | 默认值 | 不放大,可缩小 |
/* ✅ 经典布局:水平垂直居中 */
.center {
display: flex;
justify-content: center;
align-items: center;
}
/* ✅ 经典布局:等分三列 */
.three-columns .item { flex: 1; }
/* ✅ 经典布局:左侧固定 + 右侧自适应 */
.sidebar { flex: 0 0 250px; } /* 固定 250px,不伸不缩 */
.main { flex: 1; } /* 自适应填满剩余空间 */
/* ✅ 经典布局:底部固定(Sticky Footer) */
.page { display: flex; flex-direction: column; min-height: 100vh; }
.header { flex: none; }
.content { flex: 1; } /* 中间区域撑满 */
.footer { flex: none; }
💡 面试加分点:
flex: 1和flex: auto的区别——flex: 1(flex-basis: 0%)先将子项大小归零再按比例分配空间;flex: auto(flex-basis: auto)先保证子项的内容宽度,再按比例分配剩余空间。当子项内容大小不同时,两者的效果截然不同。
4. Grid 布局常用属性有哪些?
Grid(网格布局) 是二维布局模型,可以同时控制行和列。与 Flex 的一维布局不同,Grid 更适合整体页面布局和复杂网格结构。
/* ========== 容器属性 ========== */
.grid-container {
display: grid;
/* 定义列 */
grid-template-columns: 200px 1fr 200px; /* 三列:固定-自适应-固定 */
grid-template-columns: repeat(3, 1fr); /* 三等分 */
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); /* ✅ 响应式:自动填充 */
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); /* 自动适应 */
/* 定义行 */
grid-template-rows: auto 1fr auto;
/* 间距 */
gap: 20px; /* 行列间距相同 */
gap: 10px 20px; /* 行间距 列间距 */
/* 命名网格区域(直观的布局方式) */
grid-template-areas:
"header header header"
"sidebar main main"
"footer footer footer";
}
/* ========== 子项属性 ========== */
.item {
grid-column: 1 / 3; /* 跨第1列到第3列(占2列) */
grid-row: 1 / 2; /* 占第1行 */
grid-area: header; /* 对应 grid-template-areas 中的名称 */
justify-self: center; /* 单个元素水平对齐 */
align-self: center; /* 单个元素垂直对齐 */
}
auto-fill vs auto-fit 的区别:
| 关键字 | 行为 | 适用场景 |
|---|---|---|
auto-fill | 尽可能多地创建列,即使列是空的 | 固定列宽的网格 |
auto-fit | 创建的列会自动拉伸填满剩余空间 | 响应式卡片布局 |
/* ✅ 经典布局:圣杯布局(Header + Sidebar + Main + Footer) */
.holy-grail {
display: grid;
grid-template-areas:
"header header header"
"nav main aside"
"footer footer footer";
grid-template-columns: 200px 1fr 150px;
grid-template-rows: auto 1fr auto;
min-height: 100vh;
}
.header { grid-area: header; }
.nav { grid-area: nav; }
.main { grid-area: main; }
.aside { grid-area: aside; }
.footer { grid-area: footer; }
/* ✅ 经典布局:瀑布流 */
.masonry {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: 10px; /* 最小行高 */
gap: 10px;
}
.masonry-item { grid-row: span 20; } /* 每个元素占不同行数 */
/* ✅ 单行居中(最简写法) */
.center { display: grid; place-items: center; }
💡 面试加分点: Flex 和 Grid 的选择——一维用 Flex,二维用 Grid。Flex 擅长处理单行/单列中元素的对齐和分布(如导航栏、工具栏);Grid 擅长整体页面布局和需要同时控制行列的场景(如仪表盘、卡片网格)。实际项目中两者经常配合使用。
5. CSS 选择器优先级(权重)是怎么计算的?
CSS 选择器优先级决定了当多个规则应用于同一元素时,哪个规则生效。优先级使用 (a, b, c) 三元组表示,从左到右依次比较。
| 选择器类型 | 权重 | 示例 |
|---|---|---|
!important | 最高(覆盖一切) | color: red !important |
内联样式 style="" | (1, 0, 0) | <div style="color:red"> |
ID 选择器 #id | (0, 1, 0) | #header |
类 .class、伪类 :hover、属性 [type] | (0, 0, 1) | .active、:nth-child(2)、[disabled] |
标签 div、伪元素 ::before | (0, 0, 0, 1) | div、p::first-line |
通配符 *、组合符 > + ~ | 0 | *、div > p |
:where() | 0(不贡献权重) | :where(.active) |
:is() / :not() / :has() | 取参数中最高权重 | :is(#id, .class) → 按 #id 算 |
/* 优先级计算示例 */
#nav .item a:hover {
color: red;
/* ID(1) + 类(1) + 标签(1) + 伪类(1) = (0, 1, 2, 1) */
}
.container .item {
color: blue;
/* 类(1) + 类(1) = (0, 0, 2, 0) */
}
/* 结果:红色生效,因为 (0,1,2,1) > (0,0,2,0) */
/* ❌ 常见错误:试图用数量弥补等级差距 */
.a .b .c .d .e .f .g .h .i .j .k { color: blue; }
/* 11 个类选择器 = (0, 0, 11, 0) */
#id { color: red; }
/* 1 个 ID 选择器 = (0, 1, 0, 0) */
/* 结果:红色生效!ID 选择器永远高于类选择器,数量再多也无法超越 */
优先级冲突的解决策略(推荐优先级从高到低):
- 调整选择器结构使其更具体
- 利用源码顺序(后面的覆盖前面的)
- 使用
:where()降低权重 - 避免使用
!important(除非覆盖第三方库样式)
/* ✅ :where() 降低权重(CSS Layers 时代的利器) */
:where(.framework-style) { color: blue; } /* 权重为 0 */
.user-style { color: red; } /* 轻松覆盖 */
/* ✅ CSS Layers(CSS 层叠层,现代项目推荐) */
@layer framework, custom;
@layer framework { .btn { color: blue; } }
@layer custom { .btn { color: red; } } /* custom 层级更高,红色生效 */
💡 面试加分点: CSS 优先级不是简单的十进制相加(如 100 + 10),而是分级比较。一个 ID 选择器永远比任何数量的类选择器优先级高。CSS
@layer是现在管理样式优先级的新标准,它在"选择器优先级"之上增加了"层级优先级"。
6. 如何实现水平垂直居中?
水平垂直居中是 CSS 中最经典的面试题。不同方案适用场景不同,以下按推荐程度排列:
| 方法 | 是否需要知道子元素尺寸 | 适用场景 | 兼容性 |
|---|---|---|---|
| Flex | 否 | 通用,最推荐 | IE10+ |
Grid place-items | 否 | 最简洁 | 现代浏览器 |
绝对定位 + transform | 否 | 弹窗、浮层 | IE9+ |
绝对定位 + margin:auto | 是 | 已知宽高的元素 | IE8+ |
table-cell | 否 | 兼容老浏览器 | IE8+ |
/* ✅ 方法1:Flex(最推荐,通用性最强) */
.parent {
display: flex;
justify-content: center; /* 主轴居中 */
align-items: center; /* 交叉轴居中 */
}
/* ✅ 方法2:Grid(最简洁,一行搞定) */
.parent {
display: grid;
place-items: center; /* align-items + justify-items 的简写 */
}
/* ✅ 方法3:绝对定位 + transform(不需要知道子元素尺寸) */
.parent { position: relative; }
.child {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
/* translate 百分比参照的是自身的宽高 */
}
/* ✅ 方法4:绝对定位 + margin:auto(需要明确设置子元素宽高) */
.parent { position: relative; }
.child {
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
margin: auto;
width: 200px;
height: 100px;
/* 原理:四方向为 0 + margin:auto,浏览器自动计算等分 margin */
}
/* ✅ 方法5:table-cell(兼容老浏览器) */
.parent {
display: table-cell;
text-align: center; /* 水平居中(行内/行内块子元素) */
vertical-align: middle; /* 垂直居中 */
}
.child { display: inline-block; }
/* ✅ 方法6:Flex + margin:auto(也很简洁) */
.parent { display: flex; }
.child { margin: auto; }
💡 面试加分点:
transform: translate(-50%, -50%)不会引起回流(Reflow),因为transform在合成层(Composite Layer)中完成,性能优于修改top/left。实际开发中弹窗组件通常用方法3。
7. 清除浮动的方法有哪些?
浮动元素会脱离文档流,导致父元素高度塌陷(父元素的高度变为 0),影响后续布局。清除浮动就是让父元素正确包裹浮动子元素。
| 方法 | 原理 | 推荐度 |
|---|---|---|
clearfix 伪元素 | 在父元素末尾添加清除浮动的伪元素 | ⭐⭐⭐⭐⭐ |
display: flow-root | 创建 BFC | ⭐⭐⭐⭐⭐ |
overflow: hidden | 创建 BFC | ⭐⭐⭐ |
| 在末尾添加空标签 | <div style="clear:both"> | ❌ 不推荐 |
/* ✅ 方法1:clearfix 伪元素(经典方案,兼容性最好) */
.clearfix::after {
content: '';
display: block; /* 伪元素默认是 inline,clear 只对 block 有效 */
clear: both; /* 清除左右两侧浮动 */
}
/* 兼容 IE6/7 的完整版 */
.clearfix::after {
content: '';
display: table; /* 同时防止 margin 折叠 */
clear: both;
}
.clearfix { *zoom: 1; } /* IE6/7 触发 hasLayout */
/* ✅ 方法2:display: flow-root(现代方案,最推荐) */
.parent {
display: flow-root;
/* 专门为创建 BFC 设计,无副作用 */
}
/* ✅ 方法3:overflow: hidden 触发 BFC */
.parent {
overflow: hidden;
/* 注意:可能裁剪溢出内容(如下拉菜单、阴影) */
}
/* ❌ 方法4:末尾空标签(污染 HTML 结构,不推荐) */
/* <div class="parent">
<div class="child" style="float:left">内容</div>
<div style="clear:both"></div>
</div> */
💡 面试加分点: 在现代项目中,由于 Flex 和 Grid 的普及,浮动布局已经很少使用。但理解清除浮动仍然重要,因为维护老项目时经常遇到。
display: flow-root是目前最干净的解决方案。
8. position 定位有哪些值?区别是什么?
| 值 | 说明 | 是否脱离文档流 | 参照物 | 创建层叠上下文 |
|---|---|---|---|---|
static | 默认值,正常文档流 | 否 | 无(top/left 无效) | 否 |
relative | 相对自身偏移,原位置保留 | 否 | 自身原位置 | 是(配合 z-index) |
absolute | 绝对定位,脱离文档流 | 是 | 最近的 非 static 祖先 | 是 |
fixed | 固定定位,脱离文档流 | 是 | 视口(viewport) | 是 |
sticky | 粘性定位,到达阈值前正常流,到达后固定 | 否 | 滚动容器 | 是 |
/* ✅ relative:常用作定位参照物 */
.parent {
position: relative; /* 为子元素的 absolute 定位提供参照 */
}
/* ✅ absolute:常用于弹窗、下拉菜单、气泡提示 */
.dropdown {
position: absolute;
top: 100%; /* 紧贴父元素底部 */
left: 0;
z-index: 100;
}
/* ✅ fixed:常用于固定导航、回到顶部按钮 */
.back-to-top {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 999;
}
/* ✅ sticky:常用于吸顶导航、表头固定 */
.sticky-header {
position: sticky;
top: 0; /* 必须指定 top/bottom/left/right 至少一个 */
z-index: 100;
background: white;
}
/* ⚠️ sticky 的注意事项:
1. 父元素不能设置 overflow: hidden/auto/scroll(会导致 sticky 失效)
2. 必须指定 top/bottom/left/right 中至少一个方向
3. 粘性定位的范围不超过父元素的边界(父元素滚出视口后 sticky 也消失)
*/
/* ✅ fixed 的特殊情况:transform 会影响 fixed */
.parent {
transform: scale(1); /* 任何非 none 的 transform */
}
.child {
position: fixed;
/* ⚠️ 此时 fixed 不再相对于视口,而是相对于有 transform 的祖先! */
}
💡 面试加分点: 当祖先元素设置了
transform、filter、perspective、will-change等属性时,fixed定位的参照物会从视口变为该祖先元素。这是一个常见的"坑",调试时需注意。sticky在移动端表格场景(固定表头)中特别实用。
9. CSS 动画:transition 和 animation 的区别?
| 特性 | transition | animation |
|---|---|---|
| 触发方式 | 需要状态变化触发(:hover、class 切换等) | 可自动播放,无需触发 |
| 关键帧 | 只有起始和结束两个状态 | 可定义任意多个关键帧 |
| 循环播放 | ❌ 不支持 | ✅ infinite |
| 暂停控制 | ❌ 不支持 | ✅ animation-play-state: paused |
| JS 事件 | transitionend | animationstart / animationend / animationiteration |
| 适用场景 | 简单交互(hover、显隐) | 复杂动画(加载、引导) |
/* ========== transition(过渡) ========== */
.button {
background: #007bff;
transform: scale(1);
/* transition: 属性 时长 缓动函数 延迟 */
transition: background 0.3s ease, transform 0.2s ease-out;
}
.button:hover {
background: #0056b3;
transform: scale(1.05);
}
/* 常用缓动函数(timing function) */
/* ease → 先快后慢(默认) */
/* linear → 匀速 */
/* ease-in → 先慢后快(加速) */
/* ease-out → 先快后慢(减速) */
/* ease-in-out → 先慢后快再慢 */
/* cubic-bezier(0.4, 0, 0.2, 1) → 自定义(Material Design 标准) */
/* ========== animation(动画) ========== */
/* 定义关键帧 */
@keyframes fadeSlideIn {
0% { opacity: 0; transform: translateY(-20px); }
60% { opacity: 1; }
100% { opacity: 1; transform: translateY(0); }
}
.modal {
/* animation: 名称 时长 缓动 延迟 次数 方向 填充模式 */
animation: fadeSlideIn 0.3s ease forwards;
/* forwards:动画结束后保持最后一帧的状态 */
}
/* 加载旋转动画 */
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.loading {
animation: spin 1s linear infinite;
}
/* ✅ 性能优化:只动画 transform 和 opacity */
/* 这两个属性不会触发回流和重绘,由 GPU 合成层完成 */
.performant {
/* ✅ 好:GPU 加速 */
transition: transform 0.3s, opacity 0.3s;
/* ❌ 差:触发回流 */
/* transition: width 0.3s, height 0.3s, top 0.3s; */
}
/* ✅ will-change 提示浏览器优化 */
.animated-element {
will-change: transform, opacity;
/* 提前告诉浏览器该元素会变化,浏览器会提前创建合成层 */
/* ⚠️ 不要滥用,用完后移除,否则会占用额外内存 */
}
/* ✅ prefers-reduced-motion:尊重用户的动画偏好 */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
💡 面试加分点: 浏览器中只有
transform和opacity的变化可以在**合成层(Compositor Layer)**中完成,不触发回流和重绘,性能最佳。其他属性(如width、height、top、left)的动画会触发布局计算,应尽量避免。
10. CSS 变量(自定义属性)怎么使用?
CSS 变量(Custom Properties) 以 -- 开头声明,通过 var() 函数使用。与 Sass 变量不同,CSS 变量是运行时的,可以通过 JS 动态修改,也能被继承和覆盖。
| 特性 | CSS 变量 | Sass/Less 变量 |
|---|---|---|
| 作用域 | 遵循 CSS 继承和层叠 | 编译时确定 |
| 运行时修改 | ✅ JS 可动态修改 | ❌ 编译后是静态值 |
| 媒体查询中使用 | ✅ 可以在不同断点重定义 | ❌ |
| 浏览器支持 | 现代浏览器(IE 不支持) | 需要编译工具 |
/* 声明变量(通常在 :root 中定义全局变量) */
:root {
--primary-color: #007bff;
--font-size-base: 16px;
--border-radius: 4px;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
--shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
/* 使用变量 */
.button {
background-color: var(--primary-color);
font-size: var(--font-size-base);
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--border-radius);
box-shadow: var(--shadow);
}
/* ✅ 带默认值(变量未定义时使用默认值) */
.text { color: var(--text-color, #333); }
/* ✅ 变量嵌套使用 */
.card { padding: var(--spacing-md, var(--spacing-sm, 8px)); }
/* ✅ 主题切换(通过切换 data-theme 属性实现) */
:root {
--bg-color: #ffffff;
--text-color: #333333;
}
[data-theme="dark"] {
--bg-color: #1a1a1a;
--text-color: #e0e0e0;
--primary-color: #4dabf7;
}
body {
background: var(--bg-color);
color: var(--text-color);
}
/* ✅ 局部作用域(组件级变量) */
.alert {
--alert-bg: #f8d7da;
--alert-color: #842029;
background: var(--alert-bg);
color: var(--alert-color);
}
.alert.success {
--alert-bg: #d1e7dd;
--alert-color: #0f5132;
}
/* ✅ 响应式变量 */
:root { --columns: 4; }
@media (max-width: 768px) { :root { --columns: 2; } }
.grid {
grid-template-columns: repeat(var(--columns), 1fr);
}
// ✅ JS 动态修改 CSS 变量
document.documentElement.style.setProperty('--primary-color', '#ff6b6b')
// ✅ JS 读取 CSS 变量
const primaryColor = getComputedStyle(document.documentElement)
.getPropertyValue('--primary-color').trim()
💡 面试加分点: CSS 变量的作用域遵循 DOM 继承。在组件内定义的变量只对该组件及其后代生效。配合 JS 动态修改,可以轻松实现主题切换、用户自定义颜色等功能,这是 Sass 变量做不到的。
Sass 变量(SCSS 语法):
Sass 是最流行的 CSS 预处理器,变量以 $ 开头。Sass 变量在编译时解析,最终输出的 CSS 中不包含变量,全部替换为具体值。
// ========== 变量定义 ==========
$primary-color: #007bff;
$font-size-base: 16px;
$border-radius: 4px;
$spacing: (sm: 8px, md: 16px, lg: 24px); // Map 类型
// ========== 基本使用 ==========
.button {
background-color: $primary-color;
font-size: $font-size-base;
border-radius: $border-radius;
padding: map-get($spacing, sm) map-get($spacing, md);
}
// ========== 变量作用域 ==========
$color: red; // 全局变量
.container {
$color: blue; // 局部变量(仅在此选择器内生效)
color: $color; // blue
}
.other {
color: $color; // red(不受局部变量影响)
}
// 使用 !global 将局部变量提升为全局
.container {
$color: blue !global; // 修改全局变量
}
.other {
color: $color; // blue(全局已被修改)
}
// ========== !default 默认值(常用于组件库/主题定制) ==========
// 用户可以在 @use 之前覆盖这些变量
$primary-color: #007bff !default; // 仅在变量未定义时才赋值
$border-radius: 4px !default;
// ========== 变量插值 #{} ==========
$property: margin;
$direction: top;
$breakpoint: 768px;
.box {
#{$property}-#{$direction}: 10px; // 编译为:margin-top: 10px;
}
// 用于选择器和媒体查询
$component: 'alert';
.#{$component} { display: block; } // 编译为:.alert { display: block; }
@media (min-width: #{$breakpoint}) {
.container { max-width: 1200px; }
}
// ========== 变量类型 ==========
$number: 42px; // 数字(可带单位)
$string: 'hello'; // 字符串
$color: #ff6b6b; // 颜色
$boolean: true; // 布尔值
$list: 10px 20px 30px; // 列表(空格分隔)
$list2: Helvetica, Arial, sans-serif; // 列表(逗号分隔)
$map: (sm: 576px, md: 768px, lg: 1024px); // Map
$null-val: null; // 空值
// ========== 配合 Mixin 和函数使用 ==========
// 定义可复用的样式块
@mixin responsive($breakpoint) {
@media (min-width: map-get($map, $breakpoint)) {
@content; // 接收传入的内容块
}
}
.container {
width: 100%;
@include responsive('md') {
width: 750px;
}
@include responsive('lg') {
width: 1000px;
}
}
// 自定义函数
@function px-to-rem($px) {
@return calc($px / $font-size-base) * 1rem;
}
.title {
font-size: px-to-rem(24px); // 编译为:font-size: 1.5rem;
}
// ========== 模块化(@use 代替 @import) ==========
// _variables.scss(以 _ 开头的文件不会被单独编译)
$primary: #007bff;
$secondary: #6c757d;
// main.scss
@use 'variables' as vars; // 推荐使用 @use(有命名空间)
.btn { color: vars.$primary; }
@use 'variables' as *; // 去掉命名空间(直接使用)
.btn { color: $primary; }
Less 变量:
Less 变量以 @ 开头(注意和 CSS 的 @media、@keyframes 等不冲突)。Less 也是编译时处理,语法比 Sass 更接近原生 CSS。
// ========== 变量定义 ==========
@primary-color: #007bff;
@font-size-base: 16px;
@border-radius: 4px;
@spacing-sm: 8px;
@spacing-md: 16px;
// ========== 基本使用 ==========
.button {
background-color: @primary-color;
font-size: @font-size-base;
border-radius: @border-radius;
padding: @spacing-sm @spacing-md;
}
// ========== 变量作用域(类似 JS 块级作用域) ==========
@color: red;
.container {
@color: blue; // 局部变量
color: @color; // blue
}
.other {
color: @color; // red(Less 变量作用域是块级的)
}
// ========== 变量插值 @{} ==========
@property: margin;
@direction: top;
@selector: banner;
.@{selector} { // 编译为:.banner
@{property}-@{direction}: 10px; // 编译为:margin-top: 10px;
}
// URL 插值
@images-path: '../images';
.logo {
background: url('@{images-path}/logo.png');
}
// ========== 变量运算 ==========
@base: 16px;
@large: @base * 1.5; // 24px
@half: @base / 2; // 8px
@dark-primary: darken(@primary-color, 10%); // 颜色函数
.box {
width: 100% - 20px; // Less 支持混合单位运算
font-size: @large;
border-color: @dark-primary;
}
// ========== 变量作为属性值的延迟加载(Lazy Evaluation) ==========
// Less 变量在同一作用域内可以"先使用后定义"
.box {
color: @text-color; // ✅ 可以正常工作
}
@text-color: #333; // 定义在使用之后
// ========== Maps(Less 中用 Ruleset 模拟) ==========
#colors() { // 定义一组变量
primary: #007bff;
secondary: #6c757d;
success: #28a745;
}
.btn-primary {
color: #colors[primary]; // 使用
}
.btn-success {
color: #colors[success];
}
// ========== Mixin(混入) ==========
.border-radius(@radius: 4px) { // 带默认参数
border-radius: @radius;
-webkit-border-radius: @radius;
}
.card {
.border-radius(8px); // 调用 Mixin
}
// ========== 模块化 ==========
// variables.less
@primary: #007bff;
// main.less
@import 'variables'; // 引入变量文件
.btn { color: @primary; }
三者对比总结:
| 特性 | CSS 变量 | Sass(SCSS) | Less |
|---|---|---|---|
| 声明语法 | --name: value | $name: value | @name: value |
| 使用语法 | var(--name) | $name | @name |
| 作用域 | DOM 继承(可在子元素覆盖) | 块级作用域 | 块级作用域 |
| 运行时 | ✅ 浏览器原生支持 | ❌ 编译为静态 CSS | ❌ 编译为静态 CSS |
| JS 操控 | ✅ setProperty | ❌ | ❌ |
| 条件逻辑 | ❌ 不支持 | ✅ @if/@else/@for | ✅ when/each |
| 函数 | ❌ 仅 calc()等 | ✅ @function 自定义 | ✅ 内置函数丰富 |
| 插值 | ❌ | #{} | @{} |
| 模块化 | ❌ | @use/@forward | @import |
| 适用场景 | 主题切换、动态样式 | 大型项目、组件库 | 中小项目、快速开发 |
// ========== 实际项目中的最佳实践:CSS 变量 + Sass 结合使用 ==========
// _theme.scss — 用 Sass 管理设计令牌(Design Tokens)
$themes: (
light: (bg: #ffffff, text: #333333, primary: #007bff),
dark: (bg: #1a1a1a, text: #e0e0e0, primary: #4dabf7),
);
// 通过 Sass 循环生成 CSS 变量
@each $theme-name, $theme-map in $themes {
[data-theme='#{$theme-name}'] {
@each $key, $value in $theme-map {
--color-#{$key}: #{$value};
}
}
}
// 编译输出:
// [data-theme='light'] { --color-bg: #ffffff; --color-text: #333333; --color-primary: #007bff; }
// [data-theme='dark'] { --color-bg: #1a1a1a; --color-text: #e0e0e0; --color-primary: #4dabf7; }
// 使用时用 CSS 变量(支持运行时切换)
.card {
background: var(--color-bg);
color: var(--color-text);
border: 1px solid var(--color-primary);
}
11. 响应式布局的实现方式有哪些?
响应式布局的核心目标:一套代码适配不同屏幕尺寸(手机、平板、桌面)。
| 方案 | 原理 | 适用场景 |
|---|---|---|
媒体查询 @media | 根据屏幕宽度应用不同样式 | 断点式布局变化 |
rem + 根字体 | 所有尺寸相对于根字体缩放 | 移动端等比缩放 |
vw/vh 视口单位 | 百分比相对于视口尺寸 | 全屏布局、流式文字 |
clamp() | 在最小值和最大值之间自动响应 | 流体字体、流体间距 |
| Grid/Flex 自适应 | 容器自动调整子项排列 | 卡片网格、弹性布局 |
容器查询 @container | 根据父容器宽度(而非视口)响应 | 组件级响应式 |
/* ✅ 1. 媒体查询(断点式响应) */
/* 移动优先策略(Mobile First):先写小屏样式,再用 min-width 扩展 */
.container { flex-direction: column; }
@media (min-width: 768px) { /* 平板 */
.container { flex-direction: row; }
}
@media (min-width: 1024px) { /* 桌面 */
.container { max-width: 1200px; margin: 0 auto; }
}
/* 常用断点参考(Tailwind CSS 标准):
sm: 640px | md: 768px | lg: 1024px | xl: 1280px | 2xl: 1536px */
/* ✅ 2. rem + 根字体大小(移动端等比缩放) */
html { font-size: 16px; }
@media (max-width: 375px) { html { font-size: 14px; } }
.title { font-size: 1.5rem; } /* 24px / 21px */
.card { padding: 1rem; } /* 16px / 14px */
/* ✅ 3. vw/vh 视口单位 */
.hero {
width: 100vw;
height: 100vh; /* vh 在移动端有地址栏问题 */
height: 100dvh; /* ✅ dvh = dynamic viewport height,推荐 */
}
/* ✅ 4. clamp() 流体排版(最推荐的响应式字体方案) */
.title {
/* clamp(最小值, 首选值, 最大值) */
font-size: clamp(1rem, 2.5vw + 0.5rem, 2.5rem);
/* 屏幕小时 = 1rem,屏幕大时 = 2.5rem,中间自动过渡 */
}
.container {
padding: clamp(1rem, 3vw, 3rem);
/* 间距也可以用 clamp 实现流体效果 */
}
/* ✅ 5. Grid 自适应(无需媒体查询的响应式网格) */
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
/* 卡片宽度至少 280px,自动填充列数 */
}
/* ✅ 6. 容器查询(CSS 新特性,组件级响应式) */
.card-container {
container-type: inline-size; /* 声明为查询容器 */
container-name: card;
}
@container card (min-width: 400px) {
.card { flex-direction: row; } /* 容器宽度≥400px 时横排 */
}
@container card (max-width: 399px) {
.card { flex-direction: column; } /* 容器宽度<400px 时竖排 */
}
💡 面试加分点:
@container容器查询是响应式设计的里程碑。之前只能根据视口宽度响应,现在可以根据组件容器宽度响应,使组件真正可复用。移动端建议使用dvh(动态视口高度)替代vh,避免移动端地址栏收起/展开导致的布局跳动。
12. CSS 伪类和伪元素的区别?
核心区别: 伪类选择已有元素的特定状态;伪元素创建不在 DOM 中的虚拟元素。
| 对比 | 伪类(Pseudo-class) | 伪元素(Pseudo-element) |
|---|---|---|
| 语法 | 单冒号 : | 双冒号 :: |
| 作用 | 选择元素的状态 | 创建虚拟元素 |
| DOM 中 | 选择的是已存在的元素 | 创建不存在的元素 |
| 数量 | 可以链式使用多个 | 一个选择器最多一个 |
/* ========== 伪类:选择元素的状态 ========== */
/* 用户行为伪类 */
a:hover { color: red; } /* 鼠标悬停 */
input:focus { outline: 2px solid blue; } /* 获得焦点 */
button:active { transform: scale(0.98); } /* 按下状态 */
a:visited { color: purple; } /* 已访问链接 */
input:focus-visible { outline: 2px solid blue; } /* ✅ 键盘聚焦时才显示 */
/* 结构伪类(选择特定位置的元素) */
li:first-child { font-weight: bold; } /* 第一个子元素 */
li:last-child { border-bottom: none; } /* 最后一个子元素 */
li:nth-child(odd) { background: #f0f0f0; } /* 奇数行 */
li:nth-child(3n) { color: red; } /* 每隔3个 */
li:nth-child(n+4) { opacity: 0.5; } /* 第4个及之后 */
p:first-of-type { font-size: 1.2em; } /* 同类型第一个 */
div:empty { display: none; } /* 没有子元素的元素 */
/* 否定与匹配伪类 */
p:not(.special) { color: gray; } /* 排除 */
:is(h1, h2, h3) { color: navy; } /* 匹配任一(简化写法) */
:where(h1, h2, h3) { margin: 0; } /* 同 :is,但权重为 0 */
:has(.icon) { padding-left: 2em; } /* ✅ 父元素选择器(革命性!) */
/* 表单状态伪类 */
input:disabled { opacity: 0.5; }
input:checked + label { font-weight: bold; }
input:valid { border-color: green; }
input:invalid { border-color: red; }
input:placeholder-shown { border-style: dashed; } /* 显示占位符时 */
/* ========== 伪元素:创建虚拟元素 ========== */
/* ::before 和 ::after(最常用) */
.quote::before { content: '\201C'; color: #999; font-size: 2em; } /* 左引号 " */
.quote::after { content: '\201D'; color: #999; font-size: 2em; } /* 右引号 " */
/* 清除浮动经典方案 */
.clearfix::after { content: ''; display: block; clear: both; }
/* ✅ 装饰性内容(不污染 HTML) */
.required::after { content: ' *'; color: red; }
.external-link::after { content: ' ↗'; font-size: 0.8em; }
/* 其他伪元素 */
li::marker { color: #007bff; } /* 列表标记 */
::selection { background: #007bff; color: white; } /* 文字选中样式 */
input::placeholder { color: #999; } /* 占位符样式 */
::first-line { font-weight: bold; } /* 首行 */
::first-letter { font-size: 2em; float: left; } /* 首字母下沉 */
💡 面试加分点:
:has()是 CSS 中期待已久的"父元素选择器",可以根据子元素的状态来选择父元素,例如form:has(input:invalid) { border-color: red; }。:focus-visible只在键盘聚焦时显示轮廓,鼠标点击时不显示,比:focus的用户体验更好。
13. CSS 中 display 有哪些常用值?
display 属性决定了元素的显示类型,影响元素如何参与布局。
| 值 | 外部表现 | 内部表现 | 特点 |
|---|---|---|---|
block | 独占一行 | 流式布局 | 可设置宽高,默认宽度 100% |
inline | 与其他元素同行 | 流式布局 | ❌ 不可设置宽高,宽度由内容决定 |
inline-block | 与其他元素同行 | 块级布局 | ✅ 可设置宽高,不独占一行 |
flex | 独占一行 | 弹性布局 | 一维布局,子项默认横排 |
inline-flex | 与其他元素同行 | 弹性布局 | 行内级别的 flex 容器 |
grid | 独占一行 | 网格布局 | 二维布局 |
none | 不渲染 | - | 完全移除,不占空间 |
contents | 自身不渲染 | 子元素正常 | "拆箱",去掉容器保留子元素 |
flow-root | 独占一行 | BFC | 创建 BFC,无副作用 |
/* ✅ block:块级元素(div、p、h1-h6、section 默认值) */
.block { display: block; width: 100px; height: 50px; } /* 独占一行 */
/* ✅ inline:行内元素(span、a、strong 默认值) */
.inline { display: inline; }
/* ⚠️ 设置 width/height 无效,上下 margin 无效,上下 padding 不推开其他元素 */
/* ✅ inline-block:行内块(img、input 默认值) */
.tag {
display: inline-block;
padding: 4px 12px;
border: 1px solid #007bff;
border-radius: 4px;
/* 可设置宽高,同时不独占一行 → 适合标签、按钮 */
}
/* ⚠️ inline-block 元素之间会有约 4px 的空白间隙(由 HTML 中的换行/空格引起) */
/* 解决:父元素设置 font-size: 0;或使用 flex 替代 */
/* ✅ flex / grid → 见第 3、4 题 */
/* ✅ none:完全隐藏(不占空间,不可交互) */
.hidden { display: none; }
/* ✅ contents:"去掉盒子,保留内容" */
.wrapper { display: contents; }
/* 场景:想让子元素直接参与祖父的 flex/grid 布局,而不受中间容器影响 */
/* <div class="grid">
<div class="wrapper" style="display:contents">
<div>直接成为 grid 子项</div>
<div>直接成为 grid 子项</div>
</div>
</div> */
/* ✅ flow-root:创建 BFC(推荐替代 overflow:hidden) */
.bfc-container { display: flow-root; }
💡 面试加分点: CSS Display Module Level 3 规范引入了"双值语法":
display: block flex表示外部 block、内部 flex。display: contents在 CSS Grid 嵌套场景中特别有用,可以让中间的包裹元素"消失",子元素直接参与外层 Grid 布局。
14. visibility:hidden 和 display:none 和 opacity:0 的区别?
这三种方式都可以让元素"消失",但在 空间占用、事件响应、渲染性能 上差异很大:
| 特性 | display: none | visibility: hidden | opacity: 0 |
|---|---|---|---|
| 占据空间 | ❌ 不占 | ✅ 占据 | ✅ 占据 |
| 触发回流 | ✅ 切换时触发 | ❌ 不触发 | ❌ 不触发 |
| 子元素覆盖 | 不可(子元素一起消失) | ✅ 子元素可设 visible 覆盖 | ❌ 不可覆盖 |
| 事件响应 | ❌ 不可点击 | ❌ 不可点击 | ✅ 仍可点击! |
| transition 过渡 | ❌ 不支持 | ✅ 支持 | ✅ 支持 |
| 屏幕阅读器 | 不可见 | 不可见 | 可见(会朗读) |
| 适用场景 | 条件渲染(v-if) | 占位隐藏 | 淡入淡出动画 |
/* ❌ display: none → 完全移除,切换会触发回流 */
.hidden { display: none; }
/* ⚠️ visibility: hidden → 占位但不可见、不可交互 */
.invisible { visibility: hidden; }
/* ⚠️ opacity: 0 → 占位、不可见,但仍然可以交互(点击事件仍触发!) */
.transparent { opacity: 0; }
/* 如果不想响应事件,需配合 pointer-events: none */
.transparent-no-events { opacity: 0; pointer-events: none; }
/* ✅ 最佳实践:淡入淡出动画(opacity + visibility 配合) */
.modal {
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease;
/* visibility 确保隐藏时不可交互 */
/* opacity 负责视觉过渡效果 */
}
.modal.show {
opacity: 1;
visibility: visible;
}
/* ✅ 更多隐藏元素的方式 */
/* 移出视口(屏幕阅读器仍可读取,常用于无障碍) */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* clip-path 裁剪隐藏 */
.clip-hidden { clip-path: inset(50%); }
/* 缩放为 0 */
.scale-hidden { transform: scale(0); }
💡 面试加分点: 做淡入淡出动画时,不能只用
opacity(因为opacity: 0的元素仍然可以被点击),也不能只用display: none(因为不支持 transition)。正确做法是opacity + visibility配合使用。.sr-only类是无障碍开发的标准方案,让内容只对屏幕阅读器可见。
15. CSS 中如何实现文字溢出省略号?
| 方案 | 适用场景 | 兼容性 |
|---|---|---|
| 单行省略 | 标题、列表项 | 所有浏览器 |
-webkit-line-clamp | 多行文本截断 | 现代浏览器(包括 Firefox) |
| JS 方案 | 需要精确控制 | 所有浏览器 |
/* ✅ 单行省略(三件套,缺一不可) */
.ellipsis {
white-space: nowrap; /* 1. 强制不换行 */
overflow: hidden; /* 2. 超出部分隐藏 */
text-overflow: ellipsis; /* 3. 超出部分显示省略号 */
/* ⚠️ 必须有明确的宽度限制(width 或 max-width 或 flex:1) */
}
/* ✅ 多行省略(-webkit-line-clamp) */
.multi-ellipsis {
display: -webkit-box; /* 1. 弹性伸缩盒子 */
-webkit-box-orient: vertical; /* 2. 垂直排列 */
-webkit-line-clamp: 3; /* 3. 最多显示 3 行 */
line-clamp: 3; /* 标准属性(未来) */
overflow: hidden; /* 4. 超出隐藏 */
/* ⚠️ 不能设置 padding-bottom,否则可能导致省略号不显示 */
}
/* ✅ 在 Flex 布局中使用单行省略 */
.flex-ellipsis {
display: flex;
align-items: center;
}
.flex-ellipsis .text {
flex: 1;
min-width: 0; /* ⚠️ 关键!Flex 子项默认 min-width: auto */
white-space: nowrap; /* 没有 min-width: 0,省略号不会生效 */
overflow: hidden;
text-overflow: ellipsis;
}
/* ✅ Grid 布局中使用省略号 */
.grid-ellipsis {
display: grid;
grid-template-columns: 1fr auto;
}
.grid-ellipsis .text {
min-width: 0; /* Grid 子项也需要 */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* ✅ 配合 title 属性显示完整内容 */
/* <p class="ellipsis" title="这是一段很长的文字...">这是一段很长的文字...</p> */
💡 面试加分点: 在 Flex 和 Grid 布局中使用文字省略时,必须在子项上设置
min-width: 0。因为 Flex/Grid 子项的默认min-width是auto(内容的最小宽度),会阻止元素收缩到比内容更小,导致text-overflow: ellipsis失效。
16. CSS 中 z-index 的工作原理?
z-index 控制元素在z 轴(垂直于屏幕方向)上的叠放顺序。但它并不是简单的"数值越大越靠前",而是受**层叠上下文(Stacking Context)**约束。
核心规则:
z-index只对定位元素(position ≠ static)和 flex/grid 子项有效z-index的比较只在同一个层叠上下文内进行- 子元素的
z-index无法超越父元素所在层叠上下文的限制
创建新层叠上下文的条件(任一即可):
| 属性 | 条件 |
|---|---|
position + z-index | position: relative/absolute/fixed/sticky 且 z-index ≠ auto |
opacity | 值小于 1 |
transform | 不为 none |
filter | 不为 none |
isolation | isolate |
will-change | 指定了以上任一属性 |
mix-blend-mode | 不为 normal |
contain | layout 或 paint |
/* ❌ 经典"坑":子元素 z-index 再大也超不过父元素的兄弟 */
.parent-a {
position: relative;
z-index: 1; /* 创建了层叠上下文 */
}
.child-of-a {
position: absolute;
z-index: 99999; /* 只在 parent-a 内部有效! */
}
.parent-b {
position: relative;
z-index: 2; /* parent-b 整体在 parent-a 之上 */
}
/* 结果:parent-b 的内容会覆盖 child-of-a,尽管 child-of-a 的 z-index 是 99999 */
/* ✅ 同一层叠上下文内的叠放顺序(从下到上):
1. 背景和边框
2. z-index < 0 的子元素
3. 标准流中的块级元素
4. 浮动元素
5. 标准流中的行内元素
6. z-index: 0 / auto 的定位元素
7. z-index > 0 的定位元素
*/
/* ✅ 使用 isolation: isolate 精确控制层叠上下文 */
.component {
isolation: isolate;
/* 创建新的层叠上下文,内部 z-index 不会泄漏到外部 */
/* 比 position + z-index 更语义化 */
}
/* ✅ 项目中的 z-index 管理规范(使用 CSS 变量统一管理) */
:root {
--z-dropdown: 100;
--z-sticky: 200;
--z-fixed: 300;
--z-modal-backdrop: 400;
--z-modal: 500;
--z-popover: 600;
--z-tooltip: 700;
--z-toast: 800;
}
.modal-backdrop { z-index: var(--z-modal-backdrop); }
.modal { z-index: var(--z-modal); }
.tooltip { z-index: var(--z-tooltip); }
💡 面试加分点:
isolation: isolate是创建层叠上下文最"干净"的方式——不会影响元素的定位和布局,只创建新的层叠上下文。在组件库开发中,给每个组件根元素加isolation: isolate可以防止 z-index 污染。
17. CSS 预处理器(Sass/Less)的常用特性?
CSS 预处理器通过扩展 CSS 语法,提供变量、嵌套、混入、函数等编程能力,让样式代码更易维护和复用。
| 特性 | Sass/SCSS | Less | 原生 CSS 替代 |
|---|---|---|---|
| 变量 | $color: red | @color: red | --color: red ✅ |
| 嵌套 | ✅ | ✅ | ✅(CSS Nesting) |
| Mixin | @mixin / @include | .mixin() | ❌ |
| 继承 | @extend | :extend() | ❌ |
| 函数 | @function | 有限支持 | ❌ |
| 循环 | @for @each @while | each() | ❌ |
| 模块化 | @use @forward | @import | @import / @layer |
// ========== Sass(SCSS 语法)示例 ==========
// 1. 变量
$primary: #007bff;
$font-stack: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
$breakpoint-md: 768px;
// 2. 嵌套 + BEM 命名(& 代表父选择器)
.nav {
display: flex;
background: $primary;
&__item { // 编译为 .nav__item
padding: 10px 16px;
&:hover { // 编译为 .nav__item:hover
background: darken($primary, 10%);
}
&--active { // 编译为 .nav__item--active
font-weight: bold;
}
}
}
// 3. Mixin(可传参的样式片段)
@mixin flex-center($direction: row) {
display: flex;
flex-direction: $direction;
justify-content: center;
align-items: center;
}
@mixin responsive($breakpoint) {
@media (min-width: $breakpoint) { @content; }
}
.card {
@include flex-center(column);
@include responsive($breakpoint-md) {
flex-direction: row; // 768px 以上横排
}
}
// 4. 函数(返回一个值)
@function rem($px, $base: 16px) {
@return calc($px / $base * 1rem);
}
.title { font-size: rem(24px); } // 1.5rem
// 5. 继承(%placeholder 不会单独编译输出)
%button-base {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.btn-primary { @extend %button-base; background: $primary; color: white; }
.btn-danger { @extend %button-base; background: #dc3545; color: white; }
// 6. 循环
@for $i from 1 through 12 {
.col-#{$i} { width: percentage(calc($i / 12)); }
}
// 7. 条件判断
@mixin theme-color($theme) {
@if $theme == 'dark' {
background: #1a1a1a; color: #fff;
} @else {
background: #fff; color: #333;
}
}
/* ✅ 原生 CSS 嵌套(2023 年起主流浏览器支持) */
.nav {
display: flex;
& .item { padding: 10px; }
& .item:hover { color: red; }
@media (max-width: 768px) {
flex-direction: column;
}
}
💡 面试加分点: 随着 CSS 原生支持了变量(Custom Properties)、嵌套(CSS Nesting)、
@layer(层叠层)等特性,CSS 预处理器的优势在缩小。但 Mixin、函数、循环等编程能力仍然是原生 CSS 做不到的。Sass 中推荐使用@use替代@import,避免全局污染。
18. 什么是 CSS Modules?
CSS Modules 是一种 CSS 模块化方案,通过构建工具(Webpack/Vite)自动将类名编译为唯一的哈希值,实现样式的局部作用域,彻底解决类名冲突问题。
| CSS 方案 | 隔离方式 | 运行时开销 | 适用场景 |
|---|---|---|---|
| CSS Modules | 编译时生成唯一类名 | 无 | React/Vue 项目 |
| CSS-in-JS (styled-components) | 运行时生成唯一类名 | 有 | React 项目 |
| Scoped CSS (Vue) | 添加 data-v-xxx 属性选择器 | 无 | Vue 单文件组件 |
| BEM 命名规范 | 人工约定 | 无 | 所有项目 |
| Tailwind CSS | 原子类,不存在冲突 | 无 | 所有项目 |
/* Button.module.css */
.button {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.primary { background: #007bff; color: white; }
.danger { background: #dc3545; color: white; }
/* 组合类名 */
.button.primary { }
/* 全局样式(不被哈希化) */
:global(.clearfix)::after { content: ''; display: block; clear: both; }
/* 继承其他模块的类名 */
.submit { composes: button primary; font-weight: bold; }
// Button.jsx(React 中使用)
import styles from './Button.module.css'
const Button = ({ variant = 'primary', children }) => (
<button className={`${styles.button} ${styles[variant]}`}>
{children}
</button>
)
// ✅ 编译后的类名:Button_button__a1b2c3 Button_primary__d4e5f6
// 完全不会与其他组件的类名冲突
<!-- Vue 中的 Scoped CSS(类似效果) -->
<template>
<button class="button primary">按钮</button>
</template>
<style scoped>
/* scoped 会自动添加属性选择器 [data-v-7ba5bd90] */
.button { padding: 8px 16px; }
.primary { background: #007bff; }
/* 编译后:.button[data-v-7ba5bd90] { padding: 8px 16px; } */
</style>
<!-- ⚠️ Vue scoped 的深度选择器(修改子组件样式) -->
<style scoped>
:deep(.child-class) { color: red; }
/* 编译后:[data-v-7ba5bd90] .child-class { color: red; } */
</style>
💡 面试加分点: Vue 的
scoped样式底层通过给元素添加data-v-xxx属性实现隔离,但它不是真正的隔离——子组件的根元素也会带有父组件的 scope 属性。如果需要在 scoped 中修改子组件内部样式,需要使用:deep()深度选择器。
19. Tailwind CSS 的核心理念是什么?
Tailwind CSS 是一个原子化(Utility-First)CSS 框架。核心理念:每个 class 只做一件事(如 p-4 = padding: 1rem),通过组合多个原子类构建 UI,而不是编写自定义 CSS。
原子化 CSS 的优缺点:
| 优点 | 缺点 |
|---|---|
| 不用起类名,开发效率高 | HTML 类名较长 |
| 天然避免 CSS 冲突 | 需要学习类名映射 |
| CSS 体积小(按需生成) | 自定义复杂动画仍需写 CSS |
| 设计令牌(Design Token)统一 | 不适合非组件化项目 |
<!-- ❌ 传统 CSS 方式 -->
<div class="card">
<h2 class="card-title">标题</h2>
<p class="card-description">描述内容</p>
</div>
<style>
.card { padding: 16px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.card-title { font-size: 20px; font-weight: bold; color: #333; }
.card-description { color: #666; margin-top: 8px; }
</style>
<!-- ✅ Tailwind CSS 方式(无需写 CSS 文件) -->
<div class="p-4 rounded-lg shadow-md">
<h2 class="text-xl font-bold text-gray-800">标题</h2>
<p class="text-gray-600 mt-2">描述内容</p>
</div>
<!-- ✅ 响应式(前缀:sm/md/lg/xl/2xl) -->
<div class="w-full md:w-1/2 lg:w-1/3">
<!-- 手机全宽 → 平板半宽 → 桌面三分之一宽 -->
</div>
<!-- ✅ 状态变体(hover/focus/active/disabled 等) -->
<button class="bg-blue-500 hover:bg-blue-700 focus:ring-2 focus:ring-blue-300
active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed
transition-all duration-200">
按钮
</button>
<!-- ✅ 暗色模式 -->
<div class="bg-white dark:bg-gray-900 text-black dark:text-white">
自动适应暗色模式
</div>
<!-- ✅ 分组变体(group-hover) -->
<div class="group cursor-pointer">
<h3 class="group-hover:text-blue-500 transition-colors">标题</h3>
<p class="group-hover:text-gray-600">描述</p>
</div>
<!-- ✅ 任意值(方括号语法) -->
<div class="w-[calc(100%-2rem)] bg-[#1da1f2] grid-cols-[200px_1fr]">
自定义值
</div>
// tailwind.config.js - 自定义配置
export default {
content: ['./src/**/*.{html,js,jsx,tsx,vue}'], // 扫描路径
theme: {
extend: {
colors: {
brand: { 50: '#eff6ff', 500: '#3b82f6', 900: '#1e3a5f' },
},
spacing: { '18': '4.5rem' },
},
},
plugins: [],
}
💡 面试加分点: Tailwind 在生产环境中通过 PurgeCSS(现在内置为 JIT 引擎)只保留项目中实际使用的类,最终 CSS 通常只有 10-20KB(gzip 后)。与 CSS-in-JS 相比,Tailwind 没有运行时开销。
20. 如何实现一个三角形?
CSS 中实现三角形有多种方式,原理各不相同:
| 方法 | 原理 | 优点 | 缺点 |
|---|---|---|---|
border | 边框交汇处形成三角形 | 兼容性最好 | 只能做实心三角 |
clip-path | 裁剪路径 | 可做任意形状 | IE 不支持 |
linear-gradient | 渐变背景 | 灵活 | 代码不直观 |
/* ✅ 方法1:border 技巧(最经典,面试常考) */
/* 原理:当元素宽高为 0 时,四个 border 形成四个三角形 */
.triangle-up {
width: 0;
height: 0;
border-left: 20px solid transparent; /* 左边透明 */
border-right: 20px solid transparent; /* 右边透明 */
border-bottom: 40px solid #007bff; /* 底边有色 → 向上的三角 */
}
.triangle-right {
width: 0;
height: 0;
border-top: 20px solid transparent;
border-bottom: 20px solid transparent;
border-left: 40px solid #007bff; /* 左边有色 → 向右的三角 */
}
.triangle-down {
width: 0;
height: 0;
border-left: 20px solid transparent;
border-right: 20px solid transparent;
border-top: 40px solid #007bff; /* 顶边有色 → 向下的三角 */
}
/* ✅ 方法2:clip-path(推荐,可做任意多边形) */
.triangle-clip {
width: 100px;
height: 100px;
background: #007bff;
clip-path: polygon(50% 0%, 0% 100%, 100% 100%); /* 等腰三角形 */
}
/* ✅ 更多 clip-path 形状 */
.arrow-right { clip-path: polygon(0 0, 80% 50%, 0 100%); }
.pentagon { clip-path: polygon(50% 0%, 100% 38%, 82% 100%, 18% 100%, 0% 38%); }
.hexagon { clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%); }
/* ✅ 方法3:实际应用 - Tooltip 气泡箭头 */
.tooltip {
position: relative;
background: #333;
color: white;
padding: 8px 12px;
border-radius: 4px;
}
.tooltip::after {
content: '';
position: absolute;
top: 100%; /* 箭头在底部 */
left: 50%;
transform: translateX(-50%);
border: 6px solid transparent;
border-top-color: #333; /* 向下箭头 */
}
/* ✅ 方法4:实际应用 - 下拉菜单箭头 */
.dropdown-arrow::after {
content: '';
display: inline-block;
width: 8px;
height: 8px;
border-right: 2px solid currentColor;
border-bottom: 2px solid currentColor;
transform: rotate(45deg); /* 旋转 45° 变成向下的箭头 */
margin-left: 6px;
vertical-align: middle;
}
💡 面试加分点: 实际项目中三角形最常见的场景是 Tooltip 气泡箭头和下拉菜单指示器。
clip-path可以用在线工具 Clippy 快速生成路径。
21. 什么是回流(Reflow)和重绘(Repaint)?如何优化?
回流(Reflow/Layout): 当元素的几何属性(位置、大小)发生变化时,浏览器需要重新计算布局。 重绘(Repaint): 当元素的外观属性(颜色、背景、阴影)发生变化,但不影响布局时,浏览器只需重新绘制。
回流一定引起重绘,重绘不一定引起回流。回流的性能代价远大于重绘。
| 操作 | 是否触发回流 | 是否触发重绘 |
|---|---|---|
修改 width/height/margin/padding | ✅ | ✅ |
修改 color/background/box-shadow | ❌ | ✅ |
修改 transform/opacity | ❌ | ❌(合成层完成) |
读取 offsetWidth/scrollTop/getComputedStyle | ✅(强制同步布局) | - |
| 添加/删除 DOM 节点 | ✅ | ✅ |
| 窗口 resize | ✅ | ✅ |
// ❌ 错误:多次读写交替,触发多次强制同步布局
for (let i = 0; i < 100; i++) {
el.style.width = el.offsetWidth + 10 + 'px' // 每次都触发回流!
}
// ✅ 正确:先批量读取,再批量写入
const width = el.offsetWidth // 只读取一次
for (let i = 0; i < 100; i++) {
el.style.width = width + 10 * i + 'px'
}
/* ✅ 优化方案 */
/* 1. 使用 transform 代替 top/left 做动画 */
.animate { transform: translateX(100px); } /* ✅ GPU 加速 */
/* .animate { left: 100px; } */ /* ❌ 触发回流 */
/* 2. 使用 will-change 提示浏览器提前优化 */
.will-animate { will-change: transform, opacity; }
/* 3. 使用 contain 属性限制回流范围 */
.card { contain: layout style paint; }
/* 告诉浏览器:这个元素内部的变化不影响外部,可以局部回流 */
💡 面试加分点:
contain: content是性能优化的利器,它告诉浏览器该元素是一个独立的渲染边界,内部变化不会影响外部布局。Chrome DevTools 的 Performance 面板可以看到每次 Layout(回流)和 Paint(重绘)的耗时。
22. CSS 中的 margin 折叠(合并)是什么?
Margin 折叠(Margin Collapsing) 是指在垂直方向上,两个相邻块级元素的 margin 会合并为一个,取其中的较大值(而非相加)。
发生 margin 折叠的三种情况:
- 相邻兄弟元素:上元素的
margin-bottom和下元素的margin-top - 父元素与第一个/最后一个子元素:父元素的
margin-top和第一个子元素的margin-top - 空的块级元素:自身的
margin-top和margin-bottom
/* ❌ 场景1:相邻兄弟元素 margin 折叠 */
.box-a { margin-bottom: 30px; }
.box-b { margin-top: 20px; }
/* 结果:两者间距是 30px(取较大值),不是 50px */
/* ❌ 场景2:父子 margin 折叠(父没有 border/padding 时) */
.parent { margin-top: 0; }
.child { margin-top: 20px; }
/* 子元素的 margin-top 会"穿透"父元素,表现为父元素的 margin-top */
/* ✅ 解决父子 margin 折叠的方法 */
/* 方法1:父元素设置 overflow(触发 BFC) */
.parent { overflow: hidden; }
/* 方法2:父元素设置 border 或 padding(隔断 margin) */
.parent { padding-top: 1px; }
.parent { border-top: 1px solid transparent; }
/* 方法3:父元素 display: flow-root */
.parent { display: flow-root; }
/* 方法4:使用 padding 替代子元素的 margin */
.parent { padding-top: 20px; } /* 替代 .child { margin-top: 20px; } */
不会发生 margin 折叠的情况:
- 水平方向的 margin 永远不折叠
- 浮动元素、绝对定位元素不折叠
display: inline-block的元素不折叠- BFC 内外不折叠(BFC 本身就是隔离的)
- Flex/Grid 子项不折叠
💡 面试加分点: 负 margin 的折叠规则——如果一个正一个负,结果 = 正值 + 负值(即相减);如果两个都是负值,结果 = 较大绝对值的负值。Flex 和 Grid 子项之间不存在 margin 折叠,这也是现代布局的优势之一。
23. CSS 中 em、rem、%、vw/vh 等单位的区别?
| 单位 | 相对于 | 适用场景 |
|---|---|---|
px | 绝对单位(CSS 像素) | 边框、阴影等固定尺寸 |
em | 父元素的 font-size | 组件内的间距、行高 |
rem | 根元素(html)的 font-size | 全局字体、间距 |
% | 父元素的对应属性 | 宽度自适应 |
vw / vh | 视口的宽度 / 高度的 1% | 全屏布局、流体排版 |
dvh / svh / lvh | 动态 / 最小 / 最大视口高度 | 移动端全屏 |
ch | 字符 0 的宽度 | 等宽字体排版 |
lh | 当前元素的 line-height | 与行高相关的间距 |
cqw / cqh | 容器查询的宽度 / 高度的 1% | 组件级响应式 |
/* ✅ em:相对于父元素 font-size(会层层嵌套累积) */
.parent { font-size: 16px; }
.child { font-size: 1.5em; } /* 24px = 16 × 1.5 */
.grandchild { font-size: 1.5em; } /* 36px = 24 × 1.5(累积!) */
/* ⚠️ em 的坑:嵌套层级多时字体大小会不断放大/缩小 */
/* ✅ rem:相对于根元素 font-size(不会累积,更可控) */
html { font-size: 16px; }
.title { font-size: 1.5rem; } /* 24px,始终基于 html 的 16px */
.subtitle { font-size: 1.25rem; } /* 20px */
.body { font-size: 1rem; } /* 16px */
/* ✅ %:相对于父元素的对应属性 */
.child {
width: 50%; /* 父元素宽度的 50% */
padding: 5%; /* ⚠️ 百分比 padding 是相对于父元素的宽度! */
margin-top: 10%; /* ⚠️ 百分比 margin 也是相对于父元素的宽度! */
/* 上面这点经常是面试考点 */
}
/* ✅ vw/vh:视口单位 */
.full-screen { width: 100vw; height: 100vh; }
.fluid-text { font-size: 4vw; } /* 字体随视口缩放 */
/* ✅ dvh(动态视口高度):推荐移动端使用 */
.mobile-full {
height: 100dvh;
/* dvh 会考虑移动端地址栏的展开/收起,vh 不会 */
}
/* ✅ ch 单位:限制文本宽度(无障碍推荐) */
.article { max-width: 65ch; } /* 每行约 65 个字符,最佳阅读宽度 */
💡 面试加分点:
padding和margin的百分比值始终相对于包含块的宽度计算(即使是padding-top和margin-top)。这个特性可以用来实现固定宽高比的元素(如padding-top: 56.25%实现 16:9 的容器),现在更推荐使用aspect-ratio: 16/9。
24. 什么是 CSS 层叠层(@layer)?
@layer 是 CSS 层叠层(Cascade Layers),用于控制样式的优先级层级。它在选择器优先级之上增加了一层"层级优先级"管理,让样式的覆盖关系更可控。
解决的问题: 以往覆盖第三方库(如 Bootstrap、Ant Design)的样式需要写更高优先级的选择器或 !important,有了 @layer 后可以优雅地管理。
/* ✅ 定义层叠顺序(后声明的层优先级更高) */
@layer reset, base, components, utilities;
/* reset 层:最低优先级 */
@layer reset {
* { margin: 0; padding: 0; box-sizing: border-box; }
a { text-decoration: none; color: inherit; }
}
/* base 层 */
@layer base {
body { font-family: sans-serif; line-height: 1.6; }
h1 { font-size: 2rem; }
}
/* components 层 */
@layer components {
.btn { padding: 8px 16px; border-radius: 4px; }
.card { padding: 16px; border: 1px solid #eee; }
}
/* utilities 层:最高优先级 */
@layer utilities {
.hidden { display: none !important; }
.text-center { text-align: center; }
}
/* ✅ 未分层的样式优先级高于所有 @layer 中的样式 */
.my-custom-style { color: red; } /* 这条会覆盖所有层中的 color */
/* ✅ 导入第三方库到指定层(降低其优先级) */
@import url('bootstrap.css') layer(framework);
@layer framework, custom;
/* 现在 custom 层的样式会自然覆盖 framework 层 */
@layer custom {
.btn { background: #007bff; } /* 轻松覆盖 Bootstrap 的按钮样式 */
}
层叠优先级顺序(从低到高):
- 浏览器默认样式
- 有
@layer声明的样式(按声明顺序,后面的更高) - 没有
@layer的普通样式 - 内联样式
!important(反转上述顺序)
💡 面试加分点:
@layer配合@import可以把第三方 CSS 库放在低优先级层中,无需!important就能轻松覆盖。Tailwind CSS v4 已经默认使用@layer来管理样式优先级。
25. 什么是 CSS 逻辑属性?
CSS 逻辑属性(Logical Properties) 使用 inline(行内方向)和 block(块方向)替代传统的 top/right/bottom/left,使样式能自动适配不同的书写模式(如从右到左的阿拉伯语)。
| 物理属性 | 逻辑属性 | 含义 |
|---|---|---|
width | inline-size | 行内方向的尺寸 |
height | block-size | 块方向的尺寸 |
margin-left | margin-inline-start | 行内方向起点的 margin |
margin-right | margin-inline-end | 行内方向终点的 margin |
margin-top | margin-block-start | 块方向起点的 margin |
padding-left / padding-right | padding-inline | 行内方向的 padding(简写) |
text-align: left | text-align: start | 行内起点对齐 |
border-top | border-block-start | 块方向起点的 border |
top / bottom | inset-block-start / end | 块方向的偏移 |
left / right | inset-inline-start / end | 行内方向的偏移 |
/* ✅ 使用逻辑属性(国际化友好) */
.card {
margin-block: 16px; /* = margin-top + margin-bottom */
padding-inline: 24px; /* = padding-left + padding-right */
border-block-end: 1px solid #eee; /* = border-bottom */
inline-size: 300px; /* = width */
max-inline-size: 100%; /* = max-width */
}
/* ✅ 简写属性 */
.element {
margin-inline: 20px 10px; /* start: 20px, end: 10px */
padding-block: 16px; /* start 和 end 都是 16px */
inset: 0; /* = top: 0; right: 0; bottom: 0; left: 0; */
}
/* ✅ 国际化场景:RTL 布局自动适配 */
.sidebar {
margin-inline-start: 0;
margin-inline-end: 20px;
/* LTR(英语):左 0,右 20px */
/* RTL(阿拉伯语):右 0,左 20px → 自动翻转! */
}
💡 面试加分点:
inset: 0是top: 0; right: 0; bottom: 0; left: 0的简写,配合position: absolute和margin: auto可以实现居中。现代 CSS 推荐优先使用逻辑属性,尤其是需要支持国际化的项目。
26. 如何实现 1px 边框问题(移动端)?
在 Retina 屏幕(devicePixelRatio >= 2)上,CSS 的 1px 会被渲染成 2 个或 3 个物理像素,看起来比设计稿粗。
| 方案 | 原理 | 推荐度 |
|---|---|---|
transform: scale(0.5) | 伪元素 + 缩放 | ⭐⭐⭐⭐⭐ |
border-image + SVG | SVG 图片做 1px 边框 | ⭐⭐⭐ |
box-shadow | 利用阴影模拟边框 | ⭐⭐ |
0.5px | 直接写 0.5px | ⭐⭐(iOS 8+ 支持) |
/* ✅ 方案1:transform + 伪元素(最佳方案) */
.border-1px {
position: relative;
}
.border-1px::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 200%;
height: 200%;
border: 1px solid #e5e5e5;
border-radius: inherit;
transform: scale(0.5);
transform-origin: 0 0;
pointer-events: none;
box-sizing: border-box;
}
/* 只要底部边框 */
.border-bottom-1px::after {
content: '';
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 1px;
background: #e5e5e5;
transform: scaleY(0.5);
transform-origin: 0 100%;
}
/* ✅ 方案2:使用 SVG(清晰度最好) */
.border-svg {
border: 1px solid transparent;
border-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='100%25' height='100%25' fill='none' stroke='%23e5e5e5'/%3E%3C/svg%3E") 1;
}
/* ✅ 方案3:直接 0.5px(简单但兼容性有限) */
@media (-webkit-min-device-pixel-ratio: 2) {
.border-half { border-width: 0.5px; }
}
💡 面试加分点: 这是移动端开发的经典问题。
transform: scale(0.5)方案最通用,原理是先画 2 倍大小的 1px 边框,再缩放到 50%,在 2x 屏幕上刚好是 1 个物理像素。
27. 什么是 CSS 滚动吸附(Scroll Snap)?
CSS Scroll Snap 允许在滚动结束时自动将内容吸附对齐到指定位置,常用于轮播图、全屏滚动页面、横向滑动列表等。
/* ✅ 横向滑动卡片(移动端常见) */
.scroll-container {
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory; /* x 方向强制吸附 */
gap: 16px;
padding: 16px;
/* 隐藏滚动条(可选) */
scrollbar-width: none; /* Firefox */
}
.scroll-container::-webkit-scrollbar { display: none; } /* Chrome/Safari */
.scroll-item {
flex: 0 0 80%; /* 每张卡片占 80% 宽度 */
scroll-snap-align: center; /* 吸附到中心位置 */
border-radius: 12px;
}
/* ✅ 全屏滚动(每次滚动一整屏) */
.fullpage {
height: 100vh;
overflow-y: auto;
scroll-snap-type: y mandatory;
}
.section {
height: 100vh;
scroll-snap-align: start; /* 吸附到顶部 */
}
/* scroll-snap-type 的值 */
/* mandatory:必须吸附到最近的吸附点(强制) */
/* proximity:靠近吸附点时才吸附(宽松) */
/* scroll-snap-align 的值 */
/* start | center | end:对齐到容器的起点/中心/终点 */
/* ✅ 额外控制 */
.scroll-container {
scroll-padding: 20px; /* 吸附时的内边距偏移 */
}
.scroll-item {
scroll-snap-stop: always; /* 不允许跳过,每个元素都要经过 */
scroll-margin: 10px; /* 单个元素的吸附偏移 */
}
💡 面试加分点: Scroll Snap 可以替代很多 JS 轮播库(如 Swiper),实现纯 CSS 的滑动效果,性能更好。配合
scroll-behavior: smooth可以让滚动有平滑过渡效果。
28. CSS 中如何实现等比例宽高比(Aspect Ratio)?
保持元素的宽高比在响应式布局中非常重要,常见于视频播放器、图片容器、嵌入内容等。
/* ✅ 方法1:aspect-ratio 属性(现代方案,强烈推荐) */
.video-container {
width: 100%;
aspect-ratio: 16 / 9; /* 自动计算高度 = 宽度 × 9/16 */
}
.square { aspect-ratio: 1; } /* 正方形 */
.card { aspect-ratio: 3 / 4; } /* 竖版卡片 */
/* ✅ 配合 object-fit 控制图片填充方式 */
.image-container {
aspect-ratio: 1;
overflow: hidden;
}
.image-container img {
width: 100%;
height: 100%;
object-fit: cover; /* 裁剪填满(保持比例) */
/* object-fit: contain → 完整显示(可能有留白) */
/* object-fit: fill → 拉伸填满(可能变形) */
}
/* ✅ 方法2:padding-top hack(兼容老浏览器) */
.ratio-16-9 {
position: relative;
width: 100%;
padding-top: 56.25%; /* 9 / 16 × 100% = 56.25% */
/* 因为 padding 百分比是相对于宽度计算的 */
}
.ratio-16-9 > * {
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
}
/* ✅ 防止布局偏移(CLS 优化) */
img {
aspect-ratio: attr(width) / attr(height); /* 根据 HTML 属性计算 */
width: 100%;
height: auto;
}
/* 或直接在 HTML 上设置宽高属性 */
/* <img src="photo.jpg" width="800" height="600" /> */
💡 面试加分点:
aspect-ratio属性极大简化了等比例容器的实现。配合<img>标签的width和heightHTML 属性,浏览器可以在图片加载前预留空间,避免布局偏移(CLS),提升 Core Web Vitals 评分。
29. 什么是 CSS 的 contain 属性?
contain 属性告诉浏览器某个元素的内部变化不会影响外部,使浏览器可以进行局部优化(跳过不必要的回流/重绘),显著提升渲染性能。
| 值 | 含义 | 效果 |
|---|---|---|
layout | 内部布局变化不影响外部 | 回流范围局限在元素内部 |
style | 计数器和引号不影响外部 | 样式计算优化 |
paint | 内部内容不会绘制到元素边界外 | 跳过屏幕外的绘制 |
size | 元素大小不依赖子元素 | 跳过子元素的尺寸计算 |
content | = layout + style + paint | 常用简写 |
strict | = layout + style + paint + size | 最严格 |
/* ✅ 卡片组件:内部变化不影响外部 */
.card {
contain: content;
/* 等于 contain: layout style paint */
/* 浏览器知道:card 内部的 DOM 变化不需要重新计算外部布局 */
}
/* ✅ 长列表优化:每一项都是独立的渲染区域 */
.list-item {
contain: layout style paint;
/* 修改某一项不会触发其他项的回流 */
}
/* ✅ content-visibility:自动跳过屏幕外元素的渲染(更强大) */
.long-list .item {
content-visibility: auto;
contain-intrinsic-size: 0 200px; /* 预估高度,避免滚动条跳动 */
/* 屏幕外的元素不会被渲染,进入视口时才渲染 */
/* 类似虚拟滚动的效果,但无需 JS! */
}
💡 面试加分点:
content-visibility: auto是近年来最强大的 CSS 性能优化属性之一,可以让浏览器自动跳过屏幕外元素的渲染,在长页面场景下可以将首次渲染时间降低 50% 以上。需要配合contain-intrinsic-size使用,否则滚动条会抖动。
30. CSS 有哪些新特性值得关注?
以下是近几年 CSS 的重要新特性,面试中展现对新技术的了解是加分项:
/* ✅ 1. 容器查询 @container(2023) */
.card-wrapper { container-type: inline-size; }
@container (min-width: 400px) {
.card { flex-direction: row; }
}
/* ✅ 2. :has() 父元素选择器(2023) */
form:has(input:invalid) { border-color: red; }
li:has(> a:hover) { background: #f0f0f0; }
/* ✅ 3. CSS 嵌套 Nesting(2023) */
.nav {
background: white;
& .item { padding: 10px; }
&:hover { background: #f0f0f0; }
}
/* ✅ 4. 层叠层 @layer(2022) */
@layer base, components, utilities;
/* ✅ 5. 子网格 subgrid(2023) */
.parent { display: grid; grid-template-columns: repeat(3, 1fr); }
.child { display: grid; grid-template-columns: subgrid; }
/* 子元素继承父元素的网格轨道定义 */
/* ✅ 6. color-mix() 颜色混合(2023) */
.button {
background: oklch(0.7 0.15 200); /* 新的颜色空间 */
}
.button:hover {
background: color-mix(in oklch, var(--primary) 80%, black);
/* 将主色与黑色混合,实现悬停加深效果 */
}
/* ✅ 7. View Transitions API(2024) */
::view-transition-old(root) { animation: fade-out 0.3s; }
::view-transition-new(root) { animation: fade-in 0.3s; }
/* ✅ 8. Popover API(2024) */
/* <button popovertarget="menu">打开</button>
<div id="menu" popover>弹出内容</div> */
[popover] {
&:popover-open { opacity: 1; }
}
/* ✅ 9. anchor positioning 锚点定位(2024) */
.tooltip {
position: absolute;
position-anchor: --trigger;
top: anchor(bottom);
left: anchor(center);
}
/* ✅ 10. @scope 作用域(2024) */
@scope (.card) to (.card-footer) {
p { color: #333; } /* 只在 .card 到 .card-footer 之间生效 */
}
💡 面试加分点: 面试时能提到
:has()、@container、@layer、CSS Nesting 等新特性,说明你持续关注前端发展。View Transitions API让页面切换动画变得极其简单,Popover API是原生弹窗方案,减少对 JS 弹窗库的依赖。
31. CSS 有哪些样式引入方式?各有什么优缺点?
CSS 样式引入页面主要有 四种方式:外部样式表、内部样式表、行内样式和 @import 导入。
| 引入方式 | 语法 | 优先级 | 适用场景 |
|---|---|---|---|
| 外部样式表(External) | <link rel="stylesheet" href="xxx.css"> | 由选择器权重决定 | 大型项目、多页面共享样式 |
| 内部样式表(Internal) | <style>...</style> 写在 <head> 中 | 由选择器权重决定 | 单页面特有样式、关键 CSS 内联 |
| 行内样式(Inline) | style="..." 写在标签上 | ⚡ 最高(仅次于 !important) | 动态样式、JS 操控样式 |
| @import 导入 | @import url('xxx.css') 写在 CSS 中 | 由选择器权重决定 | CSS 文件模块化(不推荐) |
详细对比
① 外部样式表(推荐 ✅)
<!-- 在 <head> 中引入 -->
<link rel="stylesheet" href="styles/main.css" />
<link rel="stylesheet" href="styles/responsive.css" media="(max-width: 768px)" />
✅ 优点:
- 结构与样式分离,HTML 更干净
- 浏览器缓存,多个页面共用同一份 CSS,减少重复加载
- 便于维护和复用,一处修改全局生效
- 支持
media属性按条件加载
❌ 缺点:
- 额外的 HTTP 请求(可通过合并/内联关键 CSS 优化)
- CSS 加载完成前页面会出现无样式闪烁(FOUC)
② 内部样式表
<head>
<style>
/* 关键首屏样式(Critical CSS)——直接内联减少请求 */
body { margin: 0; font-family: system-ui, sans-serif; }
.hero { min-height: 100vh; display: flex; align-items: center; }
.hero h1 { font-size: 3rem; color: #333; }
</style>
</head>
✅ 优点: 无额外 HTTP 请求,页面加载更快(适合 Critical CSS) ❌ 缺点: 不能跨页面复用,HTML 体积增大,无法被浏览器单独缓存
③ 行内样式
<!-- 通常由 JS 动态设置 -->
<div style="color: red; font-size: 16px; margin-top: 10px;">行内样式</div>
<!-- React/Vue 中动态绑定 -->
<!-- <div :style="{ color: isActive ? 'red' : 'gray' }">动态样式</div> -->
✅ 优点: 优先级高,适合 JS 动态控制样式 ❌ 缺点: 完全违背"结构与样式分离"原则,无法复用,不能使用伪元素/伪类/媒体查询
④ @import 导入(不推荐 ❌)
/* 在 CSS 文件或 <style> 标签中使用 */
@import url('reset.css');
@import url('variables.css');
@import url('components.css');
/* 也支持媒体查询条件 */
@import url('mobile.css') screen and (max-width: 768px);
✅ 优点: CSS 文件内部模块化组织 ❌ 缺点: 串行加载(详见 Q32),增加页面渲染延迟
优先级规则(权重从高到低)
!important > 行内样式 > ID 选择器 > 类选择器 > 标签选择器 > 继承
同权重下,后声明的覆盖先声明的(就近原则)。外部样式表和内部样式表的优先级取决于它们在 HTML 中的书写顺序,后出现的优先级更高。
💡 面试加分点: 实际项目中的最佳实践是——关键首屏 CSS 用
<style>内联(减少首屏渲染阻塞),其余样式用<link>外部引入(利用缓存),避免使用@import(串行加载问题),行内样式仅用于 JS 动态设置。现代构建工具(如 Vite、Webpack)会自动提取 Critical CSS 并内联。
32. <link> 和 @import 引入 CSS 有什么区别?
这是一道经典面试题,考察你对 CSS 加载机制的理解。
| 对比项 | <link> | @import |
|---|---|---|
| 从属关系 | HTML 标签 | CSS 语法 |
| 加载时机 | 与 HTML 并行加载 | 等宿主 CSS 加载完后串行加载 |
| 兼容性 | 所有浏览器 | CSS 2.1+(IE5+ 支持) |
| DOM 可控性 | 可通过 JS 动态创建 <link> | 无法通过 JS 动态插入 |
| 功能扩展 | 支持 rel、media、preload 等属性 | 仅支持 url() 和媒体查询 |
| 放置位置 | <head> 中 | CSS 文件顶部(@charset 之后,其他规则之前) |
核心差异:加载行为
🔵 <link> 并行加载(推荐):
──────────────────────────────────────
HTML 解析: ████████████████████████████
main.css: ██████████
theme.css: ████████ ← 同时加载!
渲染: ████████
──────────────────────────────────────
总耗时短 ✅
🔴 @import 串行加载(不推荐):
──────────────────────────────────────
HTML 解析: ████████████████████████████████████
main.css: ██████████
theme.css: ████████ ← 等 main.css 加载完才开始!
渲染: ████████
──────────────────────────────────────
总耗时长 ❌(多了一次往返延迟)
示例代码
<!-- ✅ 推荐:<link> 引入 -->
<head>
<link rel="stylesheet" href="reset.css" />
<link rel="stylesheet" href="main.css" />
<link rel="stylesheet" href="responsive.css" media="(max-width: 768px)" />
<!-- 还可以预加载关键 CSS -->
<link rel="preload" href="above-fold.css" as="style" />
</head>
/* ❌ 不推荐:@import 引入(会导致串行加载) */
/* main.css */
@import url('reset.css'); /* 等 main.css 下载完才开始下载 reset.css */
@import url('variables.css'); /* 等 reset.css 下载完才开始下载 variables.css */
@import url('components.css'); /* 继续串行... */
/* 嵌套 @import 更严重:a.css → @import b.css → @import c.css → 逐级串行 */
// <link> 支持 JS 动态创建
function loadCSS(href) {
const link = document.createElement('link')
link.rel = 'stylesheet'
link.href = href
document.head.appendChild(link)
}
// 按需加载主题样式
loadCSS('themes/dark.css')
// @import 无法通过 JS 动态插入
为什么 @import 会造成串行加载?
浏览器必须先下载并解析包含 @import 的 CSS 文件,才能发现其中的 @import 声明并发起新的请求。这就形成了一个请求瀑布流(Request Waterfall),每一层 @import 都要多等待一个网络往返时间(RTT)。
💡 面试加分点: 在现代前端工程中,
@import基本被淘汰了。Sass/Less 中的@import(以及 Sass 新推荐的@use)在编译时就会合并为一个 CSS 文件,不存在运行时串行加载问题。CSS 原生的@import是运行时串行加载,性能差异巨大。如果面试官追问"有没有情况适合用 @import"——CSS 的@layer配合@import可以控制样式优先级层叠顺序,这是 2024 年的新用法。
33. 如何让 Chrome 支持小于 12px 的文字?
Chrome 浏览器(及大多数基于 Chromium 的浏览器)在中文语言环境下有一个最小字号限制:默认不允许文字小于 12px。即使你设置 font-size: 10px,实际渲染出来的也是 12px。
原因: Chrome 认为小于 12px 的中文字体可读性太差,为了保护用户阅读体验而设置了最小字号。这个限制仅在中文等 CJK 语言环境下生效,英文环境没有此限制。
解决方案
| 方案 | 原理 | 推荐度 |
|---|---|---|
transform: scale() | CSS 缩放,视觉缩小 | ⭐⭐⭐⭐⭐ 推荐 |
-webkit-text-size-adjust: none | 禁用字号调整 | ⭐⭐ 新版 Chrome 已失效 |
| 使用 SVG/Canvas | 用图形绘制文字 | ⭐⭐ 特殊场景 |
| 使用图片替代 | 将小文字做成图片 | ⭐ 不推荐 |
方案一:transform: scale()(最佳方案 ✅)
<span class="small-text">版权所有 © 2026</span>
<span class="mini-label">辅助说明文字</span>
/* 目标:显示 10px 大小的文字 */
.small-text {
font-size: 12px; /* 先设置 Chrome 允许的最小值 */
transform: scale(0.833); /* 12 × 0.833 ≈ 10px */
transform-origin: left top; /* 设置缩放基点,避免位移偏差 */
display: inline-block; /* transform 对行内元素无效,需要转换 */
}
/* 目标:显示 8px 大小的文字 */
.mini-label {
font-size: 12px;
transform: scale(0.667); /* 12 × 0.667 ≈ 8px */
transform-origin: left top;
display: inline-block;
}
注意事项:
transform: scale()只是视觉缩放,元素在文档流中仍然占据 12px 的空间- 需要手动调整
margin或用width/height修正占位偏差 transform对纯inline元素无效,需要设置display: inline-block或block
/* 完善方案:修正占位偏差 */
.scale-text-wrapper {
font-size: 12px;
display: inline-block;
transform: scale(0.833);
transform-origin: left top;
/* 修正缩放后多余的占位空间 */
/* 原始高度 × (1 - 缩放比例) 的负 margin */
margin-right: -17%; /* 大约修正水平方向多占的空间 */
}
方案二:-webkit-text-size-adjust(已失效 ⚠️)
/* 早期方案,新版 Chrome 已不支持 */
html {
-webkit-text-size-adjust: none; /* ❌ Chrome 27+ 已移除对桌面端的支持 */
}
/* 该属性现在仅在移动端 Safari 生效,用于防止横屏时自动放大文字 */
html {
-webkit-text-size-adjust: 100%; /* 移动端防止文字缩放 */
text-size-adjust: 100%;
}
方案三:SVG 文字(特殊场景)
<!-- 用 SVG 渲染小文字,不受 Chrome 字号限制 -->
<svg width="200" height="20">
<text x="0" y="12" font-size="10" fill="#999">
这段文字是真正的 10px 大小
</text>
</svg>
实际开发建议
/* 封装一个通用的小字号工具类 */
.text-xxs {
font-size: 12px;
transform: scale(0.833); /* 等效 10px */
transform-origin: left center;
display: inline-block;
}
.text-xxxs {
font-size: 12px;
transform: scale(0.667); /* 等效 8px */
transform-origin: left center;
display: inline-block;
}
/* 在 Tailwind CSS 中可以自定义插件处理 */
💡 面试加分点: 这道题考察的不仅是解决方案,更重要的是你是否知道这个限制存在以及它的原因。回答时可以提到:① Chrome 的这个限制是语言相关的(中文环境 12px,英文环境无限制);② 用户可以在
chrome://settings/fonts中手动修改最小字号设置;③transform: scale()是唯一可靠的纯 CSS 方案,但要注意它不改变元素的实际占位,需要配合布局调整。实际开发中,如果设计稿出现小于 12px 的文字,优先和设计师沟通是否可以调整为 12px。