跳到正文
前端知识库
CSS

CSS 布局、渲染与资深面试

从层叠、格式化上下文和内在尺寸深入到布局算法、层叠上下文、性能与设计系统。

9 分钟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-ratiominmax()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

动画与可访问性

优先动画 transformopacity,避免持续改变会触发布局的属性。任何非必要动画都应尊重减少动态效果偏好:

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

调试顺序

  1. 在开发者工具中确认规则是否命中、是否被覆盖。
  2. 检查元素的 computed style 和实际盒模型。
  3. 打开 Flex/Grid 调试覆盖层,确认主轴、轨道和间距。
  4. 检查包含块、滚动容器和层叠上下文。
  5. 最后才增加更高权重的规则。

权重、级联层与作用域

选择器权重通常用三列理解: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 定位祖先;但 transformfiltercontain 等属性也可能为后代建立包含块,并改变 position: fixed 的行为。

sticky 可以理解为正常流与相对滚动容器约束的组合。失效时依次检查:

  1. 是否设置了对应方向的 topinset-block-start 等阈值。
  2. 祖先是否意外成为滚动容器。
  3. 滚动容器是否真的有可滚动空间。
  4. sticky 元素是否与容器一样高,导致没有移动余量。
  5. Grid/Flex 的默认拉伸是否改变了它的实际高度。

内在尺寸与文本溢出

布局不是只有固定像素和百分比。内容拥有 min-contentmax-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: 1flex: 1 1 auto 的基础尺寸不同,不能机械视为等价。

Grid 先建立显式与隐式轨道,再根据固定、内在和弹性轨道约束分配空间。1fr 分配的是扣除固定轨道和 gap 后的剩余空间,不代表整个容器的绝对一等份。

选择布局方案时:

  • 内容沿单一主轴排列、换行后各行独立,优先 Flex。
  • 行列需要共同对齐或区域布局,优先 Grid。
  • 内容本身决定布局时利用内在尺寸和自动布局。
  • 只有界面真正发生结构变化时才增加断点。

层叠上下文与 z-index

z-index: 9999 仍可能盖不过另一个元素,因为 z-index 只在所属层叠上下文中比较。根元素、定位且非 auto 的 z-index、opacity < 1transformfilterisolation: isolate 等都可能创建新层叠上下文。

排查覆盖关系时:

  1. 找出两个元素各自最近的层叠上下文。
  2. 先比较这些上下文在共同父上下文中的层级。
  3. 只有处于同一上下文时,子元素的 z-index 才能直接比较。

应用可以建立有限的语义层级:基础内容、sticky、dropdown、modal、toast。把弹层渲染到统一 portal 根节点,并管理焦点和滚动锁定,比让每个组件自行使用任意大数字更可靠。

containcontent-visibility 与渲染性能

contain 告诉浏览器某个子树在布局、绘制、样式或尺寸上与外部隔离,可以缩小失效范围,但错误使用会改变百分比尺寸、定位或溢出行为。

.offscreen-section {
  content-visibility: auto;
  contain-intrinsic-size: auto 800px;
}

content-visibility: auto 可跳过屏幕外子树的布局和绘制,contain-intrinsic-size 为跳过期间提供估算尺寸以减少滚动跳动。它适合长页面区块,不应替代列表虚拟化的所有场景。

性能判断需要区分:

  • 修改几何属性可能使样式、布局、绘制、合成依次发生。
  • transformopacity 常可只合成,但图层提升不是绝对保证。
  • 大面积模糊、阴影、滤镜即使不布局,也可能有昂贵绘制或 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 适配浏览器工具栏变化,并根据产品体验选择 svhlvh。同时处理安全区 env(safe-area-inset-*)、软键盘、内部滚动容器和 overscroll。不能只把 100vh 当作所有移动浏览器的固定可见高度。(深入阅读:响应式设计

CSS 性能优化从哪里开始?

先在 Performance 与 Rendering 面板确认瓶颈属于样式、布局、绘制还是合成,再缩小失效范围、批量 DOM 读写、稳定尺寸、减少大面积绘制或动画成本。选择器匹配通常不是现代应用的首要瓶颈,除非 DOM 极大且样式失效频繁。(深入阅读:containcontent-visibility 与渲染性能

实践检查

  • 全局统一 box-sizing: border-box
  • 组件不依赖脆弱的深层 DOM 选择器。
  • Flex/Grid 子项在长文本下仍能正常收缩。
  • 页面在窄屏、宽屏和内容极端值下都不溢出。
  • 焦点样式清晰,颜色对比度满足阅读要求。
  • 动画不会引发布局抖动,并尊重减少动态效果设置。
  • 层叠层级和覆盖入口明确,不依赖无限提高权重。
  • 能从包含块、格式化上下文和层叠上下文解释布局问题。
  • 性能优化能对应到样式、布局、绘制或合成中的真实证据。