← 全部材料 HTML / CSS
HTML & CSS · 16 个高频考点

样式不玄学,都有规则

CSS 面试最怕的就是"我知道怎么写,但说不清为什么"。这份把每条规则还原成浏览器实际在算什么:盒模型在算宽度、BFC 在算隔离、层叠上下文在算比较范围。

01先分清楚问的是哪一类居中、清除浮动这类问题,答案取决于前提,先问清前提再答。
02认准"谁在计算"宽度是包含块在算、z-index 是层叠上下文在比、margin 是相邻盒在合并。
03说出代价每种方案都有副作用,能讲出 flow-root 比 overflow 好在哪,才是真懂。
1

盒模型:box-sizing 改的到底是什么★

width 到底指谁
box-sizing 不改变计算方式,它改变的是 width 代表哪一段。content-box(默认)下 width 只算内容区,padding 和 border 往外加;border-box 下 width 就是最终占位宽度,内容区被反向压缩。
◐ 可交互:拖 padding,看两种盒模型的实际宽度
content
border 5px padding content ↑ 按真实像素 1:1 绘制
CSS 里写的 width200px
实际占位宽度250px
内容区宽度200px
content-box 下 width 只管内容区,padding 和 border 往外加。
同样的 width: 200px,两种盒模型的实际宽度content-box(默认) width 200 + padding 20*2 + border 5*2 = 250px 占位 内容区 = 200px border-box 占位 = 200px 内容区 = 200 - 40 - 10 = 150px 断言:border-box 不会让盒子变大,只会让内容区变小。 所以"给元素加 padding 结果撑破了布局"这类问题,根源都是 content-box。
面试怎么答盒模型就是四个圈的嵌套:content、padding、border、margin。box-sizing 决定 width 属性指向哪一层。默认 content-box 指的是 content 层,所以 padding 和 border 会把盒子撑大;border-box 指的是 border 外沿,width 就是最终占位。工程里通常全局设成 border-box,写宽度时不用再心算 padding。
2

BFC:它解决的不是一个问题,是四个★

外边距塌陷只是其中最出名的一个
BFC(块级格式化上下文)是一个独立的布局容器:内部元素怎么排都影响不到外面。它能治四种病——父子外边距塌陷、父元素高度塌陷、被浮动元素覆盖、邻近元素 margin 合并。记住「隔离」这两个字,四种病都是同一件事的不同表现。

不加 BFC,两个经典 Bug

  • 父子 margin 塌陷:子元素 margin-top 漏到父元素外面,父元素整体往下走
  • 高度塌陷:子元素全浮动,父元素高度变成 0
  • 文字被浮动元素盖住一半
  • 两个相邻兄弟的 margin 取 max 而不是相加

加 BFC,四个都好了

  • 父元素 overflow: hidden,子元素的 margin 被关在里面
  • 浮动的子元素参与了父元素的高度计算
  • 左边的浮动区自成一块,文字绕排变成并排
  • 两个独立容器,margin 各自计算不合并
面试怎么答BFC 是块级格式化上下文,本质是一个隔离的布局容器,内部布局不影响外部。它能解决四类问题:父子外边距塌陷、浮动导致的高度塌陷、元素被浮动覆盖、相邻 margin 合并。触发方式里最干净的是 display: flow-root,overflow: hidden 也常用但会裁掉溢出内容。
3

浮动与清除:高度塌陷的来路和三种解法

float 本来是给文字绕排用的
浮动元素脱离文档流但不脱离文本流——所以文字会绕着它排,而父元素算高度时又不认它。这就是高度塌陷。三种解法:父元素 BFC、clearfix 伪元素、clear 属性,现代项目基本只用前两种。
浮动元素脱离文档流不占普通流的位置,父元素算高度时跳过它
→
但仍在文本流里所以行内内容会绕开它排——这是 float 的设计初衷
→
父元素高度算不出来只剩 0,后面的兄弟元素顶上来被盖住
→
解法一:父元素 BFCflow-root / overflow:hidden,父元素就会包住浮动
→
解法二:clearfix伪元素 clear:both 把父元素撑到浮动元素下方
→
解法三:clear 属性在浮动后面加一个空元素 clear:both,多一层 DOM
面试怎么答浮动的特点是脱离文档流但保留文本流,所以父元素算高度时不算它,就出现高度塌陷。解决方式三种:父元素触发 BFC 包住浮动、用 ::after 伪元素 clear: both 撑开、或者在浮动后加 clear 元素。现在推荐 display: flow-root,一行解决且没有副作用。
4

居中:八种写法,各有前提★

别背代码,先分清是哪一类居中
先问三个问题:水平还是垂直、单行还是多行、父容器尺寸知不知道。答完这三个,写法只有一个是对的。工程里 90% 的场景用 flex 居中就能覆盖,剩下的是绝对定位和 grid。
按前提选写法【父容器尺寸已知 + 子元素尺寸已知】 绝对定位 + 负 margin position:absolute; top:50%; left:50%; margin:-h/2 0 0 -w/2 【尺寸未知】 绝对定位 + transform position:absolute; top:50%; left:50%; transform:translate(-50%,-50%) 注意:translate 百分比是相对自身尺寸,所以不用知道宽高 【现代首选】 flex:display:flex; justify-content:center; align-items:center grid:display:grid; place-items:center ← 一行,最简洁 【单行文字垂直居中】 line-height = 容器高度(多行会炸) 【块级元素水平居中】 margin: 0 auto(必须有明确 width,否则块级本来就占满) 【表格单元格法】 display:table-cell; vertical-align:middle(老方案,父元素还得是 table)
面试怎么答居中先分类:水平居中,块级用 margin: 0 auto 但要给宽度,行内用 text-align。垂直居中,单行文字用 line-height 等于高度,多行就别用。通用场景用 flex 的 justify-content 加 align-items,或者 grid 的 place-items: center,一行就够。绝对定位加 transform: translate(-50%,-50%) 适合脱离文档流的弹窗,好处是不用知道自身宽高,因为 translate 的百分比是相对自己算的。
5

flex:flex: 1 到底等于什么★

一个简写藏着三个属性
flex: 1 展开是 flex: 1 1 0%——grow 1、shrink 1、basis 0%。第三个数才是关键:basis 为 0 意味着"宽度完全按剩余空间分配",而不是"在我原本宽度的基础上伸缩"。
flex:1 和 flex:auto 的实际差别容器 600px,两个子项原始宽度分别是 100px 和 200px flex: 1 (basis 0%) 每个分 300px ← 完全均分 flex: auto(basis auto) 100+500/3=266.7 ← 按原始宽度加增量 200+500/3=366.7 注意比例被保留了 结论:想均分用 flex:1,想按内容比例放大用 flex:auto。
面试怎么答flex 是三个属性的简写:grow、shrink、basis。flex: 1 展开是 1 1 0%,basis 为 0 表示从零开始分配剩余空间,所以表现是均分。flex: auto 是 1 1 auto,会保留原始宽度再放大。最常见的坑是子元素不肯收缩、省略号失效,原因是 flex 子项默认 min-width 是 auto,必须显式设成 0。
6

grid:二维布局该用它

flex 管一维,grid 管二维
flex 是一维(一行或一列内怎么排),grid 是二维(行列同时约束)。判断标准很简单:需要"上下两端也对齐"就用 grid。flex 要模拟等宽多行,得靠换行加固定宽度,很别扭。
auto-fill 与 auto-fit 的区别(高频)容器宽 900px,每列最小 220px repeat(auto-fill, minmax(220px, 1fr)) 放下 4 列 → 900/4 = 225px 余下空间也是 0,但因为 auto-fill 会"填满轨道", 如果只有 2 个元素,另外 2 个空轨道仍占位 → 元素不会拉伸 repeat(auto-fit, minmax(220px, 1fr)) 轨道数量按需收缩 → 只有 2 个元素时,它们各占一半宽度 记忆:fill = 补空轨道,fit = 折叠空轨道
面试怎么答flex 是一维布局,只关心主轴上的排列;grid 是二维,行列同时定义。需要多行多列且行列都要对齐时用 grid,比如卡片墙、整页骨架。grid 的两个高频点:一是 1fr 会自动扣掉 gap 再分剩余空间,所以比百分比加 gap 更稳;二是 repeat(auto-fill) 和 auto-fit 的区别,前者保留空轨道,后者折叠空轨道让元素拉伸。
7

层叠上下文:z-index 为什么不起作用★

z-index 只在同一个层叠上下文里比
z-index 不是一个全局的天梯榜,它只在同一个层叠上下文内部比较。父元素一旦形成了新的层叠上下文,子元素再大的 z-index 也出不去——因为整个父元素作为一个整体参与上一层的比较。
面试怎么答z-index 只在同一个层叠上下文内比较,所以调的其实是兄弟之间的关系。如果父元素因为 opacity、transform、filter 这些属性创建了新的层叠上下文,子元素再大的 z-index 也超不出父元素这一层。排查方法是往上找祖先,看谁创建了层叠上下文,然后把 z-index 提到那一层去比,或者用 isolation: isolate 明确控制。
8

选择器优先级:权重怎么算

四个层级,不是一个数字
优先级是四元组 (行内, ID, class/属性/伪类, 元素/伪元素),从左往右逐级比较,不进位——11 个 class 也赢不过 1 个 ID。比较时只看选择器本身,跟元素在 DOM 里的位置无关。
权重计算示例 a b c d * 0 0 0 0 li 0 0 0 1 ul li 0 0 0 2 .class 0 0 1 0 li.class 0 0 1 1 li.class:hover 0 0 2 1 ← 伪类算 c #id 0 1 0 0 #id .class 0 1 1 0 style="..." - - - - ← 行内直接赢(除 !important) !important ↑ 最高,仅次于浏览器默认的 important 比较:先比 a,a 大就赢;a 相同比 b,依此类推。 11 个 class = (0,0,11,0),输给 (0,1,0,0) 的 1 个 ID。
面试怎么答优先级是行内、ID、类选择器、元素选择器四段,逐段比较且不进位,所以 11 个 class 也输给 1 个 ID。伪类算第三段,伪元素算第四段。:where() 权重恒为 0,用来写可覆盖的基础样式;:is() 和 :not() 取参数里最高的那个。同权重时后声明的生效,所以引入顺序也会影响结果。
9

伪类与伪元素:单冒号还是双冒号

一个冒号是状态,两个冒号是造元素
伪类(:hover / :nth-child)描述元素的状态或位置,元素本身存在;伪元素(::before / ::first-line)凭空造出一个不在 DOM 里的部分。规范上伪元素用双冒号,伪类用单冒号;为了兼容 IE8 以前,:before 也常见,等价于 ::before。
:nth-child 和 :nth-of-type 的区别(高频)<div> <p>1</p> <span>2</span> <p>3</p> </div> p:nth-child(2) → 不选中任何元素 第 2 个子元素是 span,不是 p p:nth-of-type(2) → 选中 <p>3</p> 在"所有 p"里数第 2 个 一句话:child 按"所有兄弟"排队,of-type 按"同标签"排队。
面试怎么答伪类描述状态或位置,元素本身在 DOM 里存在,比如 hover、focus、nth-child;伪元素是凭空造出来的部分,不在 DOM 里,比如 before、after、first-line,所以必须写 content 才生效。规范上伪元素用双冒号。常考的区别是 nth-child 按所有兄弟元素排队,nth-of-type 只按同标签排队。
10

移动端适配:viewport、rem、vw 与 1px★

三个问题混在一起,要拆开答
移动端适配其实是三件事:布局宽度怎么算(viewport + 弹性单位)、字号怎么缩放(rem / vw)、物理像素怎么画细线(1px 问题)。面试里最常被追问的是第三个——为什么 1px 在高清屏上会变粗。
三种适配方案对比【rem 方案】html 字号随屏幕宽度变化,所有尺寸用 rem html { font-size: calc(100vw / 375 * 16); } /* 以 375 设计稿为基准 */ 优点:兼容老浏览器,字号可整体缩放 缺点:需要 postcss 自动换算;字体缩放在某些场景下体验差 【vw 方案】 1vw = 屏幕宽度 1%,直接写 22.5vw 表示 375 稿上的 84.375px 优点:不需要 JS,纯 CSS,现在的主流 缺点:极端宽屏下字会很大,需要 max-width 兜底 【响应式 + 弹性布局(推荐组合)】 布局用 flex / grid + 百分比、min()/max()/clamp() 字号用 clamp(14px, 4vw, 18px) 限制上下界 只对特殊元素用 vw
面试怎么答移动端适配拆成三点答。布局上通过 viewport 的 width=device-width 让布局视口等于设备宽度,再用 flex、grid、百分比做弹性布局。尺寸单位上,rem 方案是动态改 html 字号,vw 方案是直接用视口百分比,现在更推荐 vw 配合 clamp 限制上下界。1px 问题是因为 CSS 像素是逻辑像素,DPR 为 2 时 1px 会渲染成 2 个物理像素,所以看起来粗,最稳的解法是伪元素加 transform: scaleY(0.5)。
11

响应式与媒体查询

移动优先不是口号,是写法顺序
移动优先的意思是:基础样式写给手机,然后用 min-width 一层层往上加。反过来用 max-width 叫"优雅降级",两者不能混着写,否则断点会互相打架。
面试怎么答响应式布局按移动优先写:默认样式是最窄屏的,再用 min-width 媒体查询逐级增强,这样规则是叠加的,不会互相覆盖。断点不照搬框架,用内容驱动。另外按能力查询比按宽度查询更可靠,比如 prefers-color-scheme 做暗色、prefers-reduced-motion 关动效、hover: none 处理触屏没有悬停的问题。组件级别用容器查询比媒体查询更准确。
12

CSS 变量:运行时才生效的那一层

和 Sass 变量完全不是一回事
CSS 变量(自定义属性)是运行时的,能被子元素继承、能被 JS 改、能参与计算;Sass 变量是编译时的,编译完就没了。这条差别决定了主题切换、动态尺寸这些能力只有 CSS 变量能做。
CSS 变量 vs Sass 变量 CSS 变量 Sass 变量 生效时机 运行时 编译时 能否被 JS 改 能 不能(已展开) 能否被子元素覆盖 能,继承链 不能 能否用于媒体查询 不能(值没法在 查询条件里用) 能 能否参与 calc 能 能 主题切换 改一处即可 要重新编译 关键:CSS 变量存的是"字符串",浏览器在用到的地方才解析。 所以 var(--x) 里放 12px 或 red 都行,不做类型检查。
面试怎么答CSS 变量是运行时的自定义属性,存在继承链上,能被 JS 读写,也能参与 calc,所以主题切换只要在一个容器上改值。Sass 变量是编译时的,编译完就被替换掉了,没法动态改。要注意 CSS 变量不能用在媒体查询的条件里,因为媒体查询在变量解析之前就已经计算完成。
13

CSS 工程化:命名冲突怎么真正解决

四代方案,越往后隔离越强
核心问题只有一个:样式是全局的。四代方案是逐步加强隔离:BEM 命名约定 → CSS Modules 编译期改类名 → CSS-in-JS 运行时注入 → 原子化(Tailwind)。不是越新越好,看团队和项目规模。
四代方案的本质区别【BEM】靠人守规矩,类名全局但命名唯一 .card__title--large { } 缺点:靠自觉;改一个类名要全局搜 【CSS Modules】编译期把类名哈希化,作用域天然隔离 .title { } → .title_a1b2c { } 缺点:动态样式不方便;跨组件复用要 compose 【CSS-in-JS】运行时/编译期生成样式,样式跟组件走 styled.div`color: ${p => p.color}` 优点:能读 props,动态能力最强 缺点:运行时开销、SSR 抽取样式麻烦、产物体积 (现在主流转向编译期的 vanilla-extract / styleX) 【原子化】不写新 CSS,只用预定义的原子类组合 <div class="p-4 rounded-lg bg-white shadow-sm"> 优点:产物可复用、不会膨胀、没有命名问题 缺点:HTML 变长;复杂样式表达力弱;需要团队习惯
面试怎么答CSS 工程化要解决的核心是全局作用域和命名冲突。演进路线是 BEM 这种命名约定,到 CSS Modules 编译期哈希类名做隔离,再到 CSS-in-JS 让样式跟着组件走、能读 props,最后到 Tailwind 这种原子化方案,产物可复用、不膨胀。Vue 的 scoped 是加 data 属性做隔离,Shadow DOM 才是真正彻底的隔离。
14

动画:只动 transform 和 opacity★

为什么这两个属性便宜
浏览器渲染分布局、绘制、合成三步。width / top 会触发布局,background / box-shadow 触发绘制,只有 transform 和 opacity 可以跳过前两步直接合成——因为合成器线程能独立完成。
属性 → 触发哪些阶段触发 布局 + 绘制 + 合成 width / height / padding / margin / top / left / font-size → 每帧重排整棵相关子树,最贵 触发 绘制 + 合成 background-color / color / box-shadow / border-radius / visibility 只触发 合成 transform / opacity / filter(部分) → 合成器线程独立完成,不占用主线程 注意:transform 里用百分比也会触发一次布局计算来求参考尺寸, 所以高频动画优先写固定像素值或 translate3d。
面试怎么答浏览器渲染分布局、绘制、合成三步。改 width、top、margin 这类几何属性会触发重排,是最贵的;改颜色、阴影触发重绘;只有 transform 和 opacity 能跳过前两步,直接在合成器线程完成,所以流畅。需要动画的元素优先用 transform 位移和缩放,不用 top 和 left。will-change 能提前提升合成层,但会占显存,不能滥用。
15

语义化与 defer / async

这两件事经常被问到一块
语义化的实际收益不是"好看",而是无障碍、SEO、以及浏览器免费给你的交互(按钮能键盘触发、label 能关联输入框)。defer 和 async 解决的是另一个问题:脚本下载和执行要不要卡住 HTML 解析。
三种脚本加载的时序差别普通 <script> 解析 HTML ──[下载]──[执行]── 继续解析 下载和执行都阻塞解析,且放在 head 里会白屏 async 解析 HTML ──────────── 继续解析 └─[下载]─[随时执行] ← 执行时仍会阻塞解析 多个 async 之间顺序不保证 defer 解析 HTML ─────────────────────── 解析完 └─[下载]─┘ └─[按序执行] 只在 DOMContentLoaded 之前执行,顺序保证 结论:业务代码用 defer,独立统计脚本用 async。
面试怎么答语义化的收益在无障碍、SEO 和浏览器自带的交互行为,比如 button 天然可聚焦、可用键盘触发,label 能关联输入框。defer 和 async 都是并行下载,区别在执行时机:defer 等 HTML 解析完按顺序执行,保证依赖顺序,适合业务代码;async 下载完立刻执行,顺序不保证,适合独立的统计脚本。
16

Web Components 与 Shadow DOM★

真隔离,代价也真的大
Web Components 是四个浏览器原生能力的组合:Custom Elements(自定义标签)、Shadow DOM(样式和 DOM 隔离)、HTML Template(可复用的片段)、ES Module(分发方式)。它最大的卖点是框架无关——同一个组件能在 React、Vue 甚至纯 HTML 里用。
Shadow DOM 隔离了什么,没隔离什么隔离了 外部选择器进不来(.card { color: red } 影响不到里面) 内部的样式出不去 document.querySelector 拿不到 shadowRoot 里的节点 没隔离 CSS 继承属性仍会穿透(font-family、color 会继承进去) CSS 自定义属性(--x)可以穿透,这是官方留的主题接口 事件会穿透(composed: true 的事件能冒泡到外面) 所以组件库用 Shadow DOM 时,主题化必须走 CSS 变量, 不能指望外部写个类名改样式。
面试怎么答Web Components 是 Custom Elements、Shadow DOM、Template、ES Module 四件套,最大价值是框架无关。Shadow DOM 提供真正的样式隔离,外部选择器进不去、内部出不来,但 CSS 继承属性和自定义属性是能穿透的,所以主题化要走 CSS 变量。代价是 SSR 不支持、和 React 的属性传递有兼容问题,所以更适合跨框架的组件库而不是普通业务。
17

速答清单

考前十分钟只看这一页
01box-sizing 改了什么?
改的是 width 指向哪一层。content-box 下 width 只算内容区,padding 和 border 往外加;border-box 下 width 是最终占位宽度,内容区被反向压缩。
02margin 会参与 width 计算吗?
不会。margin 永远在盒子外面,所以 width: 100% 再加 margin 一定会溢出。
03BFC 能解决哪四类问题?
父子外边距塌陷、浮动导致的高度塌陷、元素被浮动覆盖、相邻元素 margin 合并。本质都是"隔离"。
04最干净的 BFC 触发方式?
display: flow-root。没有 overflow: hidden 会裁剪溢出内容的副作用。
05三栏布局现在怎么做?
grid 的 grid-template-columns 一行搞定。老方案是浮动 + 负 margin(圣杯)或双飞翼,核心差别是中间栏有没有多包一层。
06水平垂直居中,最推荐哪种?
grid 的 place-items: center,一行同时管两个方向。弹窗类脱离文档流的用 absolute + transform: translate(-50%,-50%)。
07flex: 1 展开是什么?
flex: 1 1 0%。第三个值 basis 为 0,表示从零开始分配剩余空间,所以表现是均分。flex: auto 是 1 1 auto,保留原始宽度再放大。
08flex 子项不肯收缩、省略号失效怎么修?
给子项加 min-width: 0。默认值是 auto,意思是不能小于内容最小宽度。
09z-index 为什么不生效?
大概率父元素创建了新的层叠上下文(opacity、transform、filter、will-change 等),子元素的 z-index 出不去这一层。
10优先级 11 个 class 能赢 1 个 ID 吗?
不能。优先级是四段逐级比较,不进位。
11nth-child 和 nth-of-type 的区别?
child 按所有兄弟元素排队,of-type 只按同标签排队。
121px 边框为什么在高清屏变粗?
CSS 像素是逻辑像素,DPR 为 2 时 1px 渲染成 2 个物理像素。解法是伪元素 + transform: scaleY(0.5),最稳。
13CSS 变量能用在媒体查询里吗?
不能。媒体查询在变量解析之前就计算完成,所以 @media (min-width: var(--bp)) 无效。
14为什么动画只用 transform 和 opacity?
渲染分布局、绘制、合成三步。这两个属性可以跳过前两步,在合成器线程独立完成,不占主线程。
15defer 和 async 的区别?
都并行下载。defer 等 HTML 解析完按顺序执行,保证依赖顺序;async 下载完立刻执行,顺序不保证。业务代码用 defer。
16Shadow DOM 隔离了什么?
外部选择器进不来、内部样式出不去,但 CSS 继承属性和自定义属性仍能穿透,所以主题化要走 CSS 变量。