跳到主要内容

CSS 与布局

盒模型​

content-box(标准,默认):width = 内容宽度
border-box(IE/怪异): width = 内容 + padding + border

实践中几乎所有项目都会全局设 * { box-sizing: border-box } —— 因为「设了宽度就是最终占位宽度」更符合直觉,加 padding 不会撑开布局。

🔥 BFC​

块级格式化上下文 —— 一个独立的渲染区域,内部元素的布局不影响外部。

怎么创建 BFC:

  • overflow 不为 visible(最常用的 overflow: hidden)
  • display: flow-root(专门用来创建 BFC 的现代写法,无副作用)
  • float 不为 none
  • position: absolute / fixed
  • display: inline-block / flex / grid / table-cell

BFC 解决三个经典问题:

1. 外边距塌陷(margin collapse)

<!-- 父子元素的 margin-top 会合并 -->
<div style="margin-top: 20px">
<div style="margin-top: 30px">实际只有 30px,不是 50px</div>
</div>

父元素创建 BFC(或加 padding/border)后就不会塌陷。相邻兄弟元素的垂直 margin 也会合并(取较大值),这个无法用 BFC 解决,只能用单方向 margin 约定(如统一只用 margin-bottom)。

2. 包含浮动元素(清除浮动)

浮动元素脱离文档流,父元素高度会塌陷为 0。父元素创建 BFC 后会把浮动子元素包含进去。

现代写法是 display: flow-root,老写法是经典的伪元素清除:

.clearfix::after { content: ''; display: block; clear: both; }

3. 阻止元素被浮动元素覆盖 —— 两栏布局里,给右侧元素创建 BFC 可以让它不被左侧浮动元素压住。

层叠上下文与 z-index​

z-index 不生效的常见原因(高频实战问题):

  1. 元素不是定位元素 —— z-index 只对 position 不为 static 的元素(以及 flex/grid 子项)生效
  2. 父元素创建了层叠上下文 —— 子元素的 z-index 只在父层叠上下文内比较。父元素 z-index: 1,子元素 z-index: 9999 也压不过另一个 z-index: 2 的兄弟元素的子元素
  3. 被 transform、opacity < 1、filter、will-change 意外创建了层叠上下文

创建层叠上下文的条件:根元素、position 非 static 且 z-index 非 auto、opacity < 1、transform / filter / perspective 非 none、isolation: isolate、will-change、flex/grid 子项且 z-index 非 auto。

同一层叠上下文内的层叠顺序(从下到上):

背景和边框 → 负 z-index → block 块级盒 → float → inline/inline-block
→ z-index: 0 / auto 的定位元素 → 正 z-index

所以 z-index: 0 和 z-index: auto 看起来一样,但 auto 不创建层叠上下文,0 会创建。

Flex 与 Grid​

Flex 是一维布局(主轴 + 交叉轴),Grid 是二维布局(行 + 列)。

/* 常考的三个简写 */
flex: 1; /* = flex-grow:1 flex-shrink:1 flex-basis:0% */
flex: auto; /* = 1 1 auto */
flex: none; /* = 0 0 auto */

flex: 1 和 flex: auto 的差别:flex-basis 分别是 0% 和 auto。前者忽略内容本身的尺寸平均分配空间;后者先按内容尺寸再分配剩余空间。「几个 flex:1 的子项宽度不一样」的原因通常是内容撑开了(要配合 min-width: 0 或 overflow: hidden)。

:::warning flex 子项不收缩的坑 flex-shrink 的计算基于 min-width,而 flex 子项的 min-width 默认是 auto(取决于内容),所以长文本或大图会撑破容器。

解法:给子项加 min-width: 0(或 overflow: hidden)。这是 flex 布局最常见的坑,尤其在做「左固定右自适应 + 右侧文本超出省略」时必踩。 :::

Grid 的关键能力:

.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); /* 响应式网格,无需媒体查询 */
gap: 16px;
grid-template-areas: "header header" "sidebar main"; /* 语义化布局 */
}

repeat(auto-fill, minmax(200px, 1fr)) 是 Grid 最实用的一个模式 —— 自动根据容器宽度决定列数,每列最小 200px,剩余空间平分。

auto-fill vs auto-fit:项目不足时 auto-fill 保留空轨道,auto-fit 折叠空轨道(现有项目会拉伸填满)。

居中的各种方式​

/* 1. Flex(最常用) */
.parent { display: flex; justify-content: center; align-items: center; }

/* 2. Grid(最简洁) */
.parent { display: grid; place-items: center; }

/* 3. 绝对定位 + transform(不需要知道尺寸) */
.child { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }

/* 4. 绝对定位 + margin auto */
.child { position: absolute; inset: 0; margin: auto; width: 100px; height: 100px; }

水平垂直居中一个未知尺寸的元素 —— 方案 1、2、3 都行。方案 3 的 transform: translate(-50%) 百分比是相对自身尺寸,这是它不需要知道尺寸的原因(而 margin-left: -50% 的百分比相对父元素,所以不行)。

单位与响应式​

单位相对于
px绝对(CSS 像素)
em父元素(或自身,用于 font-size 时)的字号 —— 会嵌套累乘,容易失控
rem根元素(html)的字号 —— 可控,移动端适配常用
%父元素对应属性
vw / vh视口宽 / 高的 1%
dvh / svh / lvh动态 / 小 / 大视口高度 —— 解决移动端地址栏伸缩导致 100vh 跳动的问题

移动端适配方案:

  1. rem + 动态设置根字号(document.documentElement.style.fontSize)—— 老方案,需要 JS
  2. vw —— 纯 CSS,配合 postcss-px-to-viewport 自动转换
  3. clamp() —— font-size: clamp(14px, 2.5vw, 18px),一行实现流式缩放 + 上下限
  4. @media + 固定断点 —— 适合桌面端的几档布局
  5. 容器查询 @container —— 按容器宽度而非视口宽度响应,组件级响应式的正解

1px 边框问题 —— 高 DPR 屏幕上 1 CSS px = 2~3 物理像素,看起来偏粗。解法:transform: scaleY(0.5) 配合伪元素,或用 border-width: 0.5px(iOS 支持),或用 box-shadow。

动画性能​

只有 transform 和 opacity 能跳过布局和绘制,直接在合成线程执行(见浏览器原理的重排重绘)。

/* ❌ 触发重排,每帧都要重新布局 */
.bad { transition: left 0.3s; }

/* ✅ 只触发合成 */
.good { transition: transform 0.3s; }

will-change 提前告知浏览器,让它预先创建合成层。但不要滥用 —— 每个合成层都要占独立的位图内存,加多了内存暴涨反而更卡。用完应该移除。

CSS 动画 vs JS 动画 —— CSS 动画(transition/animation)由浏览器优化,简单动画性能更好且不占主线程;JS 动画(requestAnimationFrame、GSAP)控制精细,适合复杂时间线、需要根据状态动态计算的场景。你简历上用 GSAP 做复杂交互动效,被问到时可以说清这个取舍。

requestAnimationFrame 而不是 setTimeout —— 见事件循环与异步。

其他高频点​

选择器权重:!important > 内联(1000)> ID(100)> 类/属性/伪类(10)> 元素/伪元素(1)。同权重后写的覆盖先写的。权重不进位 —— 11 个类(11 × 10)仍然干不过 1 个 ID(100)。

现代替代方案 —— :where() 权重为 0(用于低权重的默认样式),:is() 取内部最高权重,@layer 可以显式控制层级顺序(比权重更可控,是 CSS 架构的新方向)。

display: none / visibility: hidden / opacity: 0:

占位可点击触发
display: none❌❌重排
visibility: hidden✅❌重绘
opacity: 0✅✅ 仍可点击合成(可动画)

opacity: 0 的元素仍能响应事件,这是「看不见但点得到」类 bug 的来源,要配合 pointer-events: none。

CSS 方案对比 —— CSS Modules(构建时生成唯一类名,零运行时)、Tailwind(原子类,产物小、无需命名)、CSS-in-JS(styled-components / emotion,动态样式强但有运行时开销,且和 RSC 不兼容)、原生 CSS 变量 + :has() 等新特性(现在原生能力已经覆盖大部分需求)。

趋势值得一提:CSS-in-JS 因为运行时开销和 Server Components 兼容性问题正在退潮,Tailwind 和零运行时方案(vanilla-extract、Linaria)在上升。

:has() —— 「父选择器」,.card:has(img) 选中包含图片的卡片。它让很多过去必须用 JS 的场景变成纯 CSS。