CSS 布局、渲染与资深面试
从层叠、格式化上下文和内在尺寸深入到布局算法、层叠上下文、性能与设计系统。
CSS 的难点通常不是属性数量,而是理解规则如何竞争、尺寸如何计算,以及布局上下文如何约束元素。
层叠如何决定结果
当多条规则命中同一元素时,浏览器依次考虑来源与重要性、级联层、选择器权重、作用域接近程度和源码顺序。
/* 权重低,易于覆盖 */
.button {
color: #0f766e;
}
/* 增加状态类,而不是不断叠加选择器 */
.button.is-dangerous {
color: #dc2626;
}
选择器应尽量短且稳定。不要依赖深层 DOM 结构,也不要用 !important 修补不清楚的层叠关系。
可使用级联层明确不同样式来源的优先级:
@layer reset, base, components, utilities;
@layer components {
.button {
border-radius: 0.5rem;
}
}
盒模型与尺寸
默认盒模型中,声明的 width 只包含内容区域,内边距和边框会额外增加实际宽度。项目通常统一使用:
*,
*::before,
*::after {
box-sizing: border-box;
}
此时 width 包含 content、padding 和 border,组件尺寸更容易推导。
几个容易混淆的概念:
width: 100%相对包含块的内容宽度计算。min-width: 0允许 Flex 或 Grid 子项收缩,常用于修复长文本溢出。- 外边距可能发生折叠,Flex 和 Grid 容器中的项目不会折叠外边距。
overflow: hidden不只是裁剪内容,也会创建新的块级格式化上下文。
正常流与定位
优先使用正常文档流完成布局。绝对定位适合浮层、角标等脱离流后不会影响周围结构的元素。
.card {
position: relative;
}
.card__badge {
position: absolute;
inset-block-start: 0.75rem;
inset-inline-end: 0.75rem;
}
position: sticky 依赖最近滚动容器;祖先设置 overflow: hidden/auto 时,要确认这是否改变了预期滚动上下文。
Flex:一维排列
Flex 适合工具栏、导航、按钮组等沿一个主轴排列的界面。
.toolbar {
display: flex;
align-items: center;
gap: 0.75rem;
}
.toolbar__title {
min-width: 0;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
justify-content控制主轴。align-items控制交叉轴。flex: 1常表示1 1 0%,项目会共同分配空间。- 子项长文本撑破容器时,先检查
min-width: auto,通常需要设置min-width: 0。
Grid:二维布局
Grid 适合同时控制行列的页面骨架和列表布局。
.article-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr));
gap: 1rem;
}
auto-fit + minmax() 可以让列数随可用空间变化,减少只为改变列数而增加的媒体查询。
页面侧栏可以写成:
.shell {
display: grid;
grid-template-columns: 18rem minmax(0, 1fr);
min-height: 100vh;
}
响应式设计
响应式不是把桌面版等比例缩小,而是根据空间重新安排优先级。
.page {
padding-inline: 1rem;
}
@media (min-width: 48rem) {
.page {
padding-inline: 2rem;
}
}
优先从窄屏基础样式开始,再用 min-width 增强。固定格式组件应明确尺寸约束,例如 aspect-ratio、minmax()、max-width,防止内容变化造成布局跳动。
现代组件还可使用容器查询:
.article-panel {
container-type: inline-size;
}
@container (min-width: 36rem) {
.article-meta {
display: grid;
grid-template-columns: 1fr auto;
}
}
自定义属性与设计令牌
:root {
--color-surface: 0 0% 100%;
--color-text: 222 30% 13%;
--space-page: 1.5rem;
}
.panel {
color: hsl(var(--color-text));
background: hsl(var(--color-surface));
padding: var(--space-page);
}
自定义属性参与运行时级联,适合主题、组件参数和跨层共享令牌。命名应表达用途,例如 --color-danger,而不是绑定具体颜色的 --red。
动画与可访问性
优先动画 transform 和 opacity,避免持续改变会触发布局的属性。任何非必要动画都应尊重减少动态效果偏好:
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
调试顺序
- 在开发者工具中确认规则是否命中、是否被覆盖。
- 检查元素的 computed style 和实际盒模型。
- 打开 Flex/Grid 调试覆盖层,确认主轴、轨道和间距。
- 检查包含块、滚动容器和层叠上下文。
- 最后才增加更高权重的规则。
权重、级联层与作用域
选择器权重通常用三列理解:ID;类、属性和伪类;类型和伪元素。比较时从左到右逐列比较,不是简单十进制相加:
#app .button {} /* 1-1-0 */
.toolbar .button:hover {} /* 0-3-0 */
:where(.dialog .title) {} /* 0-0-0 */
:is(#app, .page) h2 {} /* 1-0-1,取参数中最高权重 */
where() 的权重始终为零,适合发布容易覆盖的基础规则;is()、not()、has() 本身不增加权重,但参数会贡献权重。
级联层比较发生在选择器权重之前。普通声明中,未分层规则优先于已分层规则;层之间后声明的层优先。!important 会反转层顺序,因此不能把它当作普通提权工具。
@layer reset, theme, components, overrides;
@layer components {
:where(.button) {
color: var(--button-text);
}
}
成熟样式架构会约束“规则从哪里覆盖”:重置、基础令牌、组件、工具类、产品覆盖各自处于明确层级。这样比不断增加选择器权重更可预测。
格式化上下文与外边距折叠
块级格式化上下文(BFC)是一块独立布局区域。常见创建方式包括 display: flow-root、非 visible/clip 的 overflow、浮动元素以及 Flex/Grid 项等。
BFC 常用于:
- 包含内部浮动,避免父元素高度塌陷。
- 阻止自身内容环绕外部浮动。
- 隔离相邻块级元素的外边距折叠。
.article-body {
display: flow-root;
}
不要为了创建 BFC 一律使用 overflow: hidden,它同时会裁剪阴影、弹层和焦点轮廓。flow-root 更直接表达布局意图。
垂直外边距折叠只发生在特定块级流场景。父子之间没有边框、内边距、行内内容或新格式化上下文隔开时,首尾 margin 也可能与父元素折叠。遇到“margin 跑到父元素外面”,先判断折叠条件,而不是随意加 padding: 1px。
包含块、定位与粘性失效
百分比尺寸和绝对定位的参照由包含块决定。绝对定位元素通常相对最近的非 static 定位祖先;但 transform、filter、contain 等属性也可能为后代建立包含块,并改变 position: fixed 的行为。
sticky 可以理解为正常流与相对滚动容器约束的组合。失效时依次检查:
- 是否设置了对应方向的
top、inset-block-start等阈值。 - 祖先是否意外成为滚动容器。
- 滚动容器是否真的有可滚动空间。
- sticky 元素是否与容器一样高,导致没有移动余量。
- Grid/Flex 的默认拉伸是否改变了它的实际高度。
内在尺寸与文本溢出
布局不是只有固定像素和百分比。内容拥有 min-content、max-content 等内在尺寸,Flex/Grid 会把它们纳入约束求解。
.shell {
display: grid;
grid-template-columns: minmax(14rem, 20rem) minmax(0, 1fr);
}
.title {
overflow-wrap: anywhere;
}
第二列使用 minmax(0, 1fr) 是因为 Grid 项默认最小尺寸通常是 auto,长单词或代码可能让轨道拒绝缩小。Flex 项中的 min-width: 0 解决的是同一类自动最小尺寸问题。
常见函数的语义:
min()选择较小结果,适合把宽度限制在容器内。max()选择较大结果,适合保证最小触摸尺寸。clamp(min, preferred, max)在上下限之间钳制流体值。fit-content()允许内容在最小与最大内容尺寸之间收缩。
文本截断前要先问内容能否丢失。单行省略应提供可访问的完整信息来源;正文通常更适合换行而不是隐藏。
Flex 与 Grid 算法的面试解释
Flex 先取得每个项目的 flex base size,再计算容器的剩余空间。正剩余空间按 flex-grow 分配,负剩余空间结合 flex-shrink 与基础尺寸收缩,并受最小/最大尺寸约束。flex: 1 与 flex: 1 1 auto 的基础尺寸不同,不能机械视为等价。
Grid 先建立显式与隐式轨道,再根据固定、内在和弹性轨道约束分配空间。1fr 分配的是扣除固定轨道和 gap 后的剩余空间,不代表整个容器的绝对一等份。
选择布局方案时:
- 内容沿单一主轴排列、换行后各行独立,优先 Flex。
- 行列需要共同对齐或区域布局,优先 Grid。
- 内容本身决定布局时利用内在尺寸和自动布局。
- 只有界面真正发生结构变化时才增加断点。
层叠上下文与 z-index
z-index: 9999 仍可能盖不过另一个元素,因为 z-index 只在所属层叠上下文中比较。根元素、定位且非 auto 的 z-index、opacity < 1、transform、filter、isolation: isolate 等都可能创建新层叠上下文。
排查覆盖关系时:
- 找出两个元素各自最近的层叠上下文。
- 先比较这些上下文在共同父上下文中的层级。
- 只有处于同一上下文时,子元素的 z-index 才能直接比较。
应用可以建立有限的语义层级:基础内容、sticky、dropdown、modal、toast。把弹层渲染到统一 portal 根节点,并管理焦点和滚动锁定,比让每个组件自行使用任意大数字更可靠。
contain、content-visibility 与渲染性能
contain 告诉浏览器某个子树在布局、绘制、样式或尺寸上与外部隔离,可以缩小失效范围,但错误使用会改变百分比尺寸、定位或溢出行为。
.offscreen-section {
content-visibility: auto;
contain-intrinsic-size: auto 800px;
}
content-visibility: auto 可跳过屏幕外子树的布局和绘制,contain-intrinsic-size 为跳过期间提供估算尺寸以减少滚动跳动。它适合长页面区块,不应替代列表虚拟化的所有场景。
性能判断需要区分:
- 修改几何属性可能使样式、布局、绘制、合成依次发生。
transform与opacity常可只合成,但图层提升不是绝对保证。- 大面积模糊、阴影、滤镜即使不布局,也可能有昂贵绘制或 GPU 成本。
- CSS 文件体积不是唯一指标,复杂 DOM、频繁失效和大绘制区域同样关键。
设计系统中的 CSS 边界
设计令牌应分层:原始值记录色板和尺度,语义令牌表达用途,组件令牌处理局部变体。例如组件消费 --color-text-danger,而不是直接依赖 --red-600。
组件样式要定义清晰契约:
- 哪些属性由组件拥有,哪些允许容器控制。
- 尺寸是否由内容、容器还是显式 prop 决定。
- 长文本、空内容、国际化和 200% 缩放下的行为。
- 交互状态是否覆盖 hover、focus-visible、active、disabled 与 forced-colors。
- 主题切换是否只改变令牌,避免复制整套组件规则。
CSS Modules、原子类、CSS-in-JS 和原生级联都可以工作。资深回答应比较运行时成本、静态提取、共置体验、覆盖策略、缓存、服务端渲染与团队约束,而不是宣称某一种方案永远最好。
资深面试追问
如何实现水平垂直居中?
先确认对象和约束。普通单项居中可用 Grid 的 place-items: center;行内内容可用布局与文本对齐;未知尺寸浮层可用 inset 配合 margin auto 或 transform;多项分布要先确定主轴。面试重点是解释方案依赖的包含块、可用空间和是否脱离文档流。(深入阅读:居中方案对比)
为什么 height: 100% 有时无效?
百分比高度通常需要包含块具有可确定高度;父元素高度由内容自动撑开时,百分比可能无法解析为预期值。视口骨架可使用 min-height: 100dvh,Flex/Grid 子区则通过轨道分配空间,并注意滚动子项的 min-height: 0。(深入阅读:包含块、定位与粘性失效)
如何实现稳定的移动端全高页面?
优先动态视口单位 dvh 适配浏览器工具栏变化,并根据产品体验选择 svh 或 lvh。同时处理安全区 env(safe-area-inset-*)、软键盘、内部滚动容器和 overscroll。不能只把 100vh 当作所有移动浏览器的固定可见高度。(深入阅读:响应式设计)
CSS 性能优化从哪里开始?
先在 Performance 与 Rendering 面板确认瓶颈属于样式、布局、绘制还是合成,再缩小失效范围、批量 DOM 读写、稳定尺寸、减少大面积绘制或动画成本。选择器匹配通常不是现代应用的首要瓶颈,除非 DOM 极大且样式失效频繁。(深入阅读:contain、content-visibility 与渲染性能)
实践检查
- 全局统一
box-sizing: border-box。 - 组件不依赖脆弱的深层 DOM 选择器。
- Flex/Grid 子项在长文本下仍能正常收缩。
- 页面在窄屏、宽屏和内容极端值下都不溢出。
- 焦点样式清晰,颜色对比度满足阅读要求。
- 动画不会引发布局抖动,并尊重减少动态效果设置。
- 层叠层级和覆盖入口明确,不依赖无限提高权重。
- 能从包含块、格式化上下文和层叠上下文解释布局问题。
- 性能优化能对应到样式、布局、绘制或合成中的真实证据。