1 基础知识
尽可能依靠浏览器的布局引擎,以获得更好的性能。在采用硬编码尺寸或复杂的媒体查询之前,请先尝试使用固有尺寸、逻辑属性和 aspect-ratio。
1.1 使用哪种布局模式?
从上到下遍历决策树,并在找到第一个匹配项时停止。请注意,布局可以相互嵌套,每个决策都基于该容器的用例。
- 是简单的一行或一列内容吗?使用Flexbox - 一维、内容优先、内容沿单轴分布。
- 嵌套元素是否需要与其祖父级网格的轨道对齐?使用 subgrid - 二维,关系优先,继承父级轨道,使孙级元素可以跨同级元素对齐。
- 页面或组件结构是否复杂,同时包含行和列?使用网格 - 2D、布局优先,您定义骨架,内容填充骨架。
- 内容是否为长篇散文,应拆分为均衡的列?使用多列 - 一维流,报纸样式。
- 物品的高度是否各不相同,需要紧密包装?现在,请将 grid 与
grid-auto-flow: dense搭配使用;仅当原生瀑布流(又称“网格通道”)在您的基准目标中发布时,才使用它(请参阅第 8 节)。 - 是否需要让某个元素悬浮在页面上方,并与触发器保持空间上的关联,即使跨越 DOM 边界或堆叠上下文也是如此?使用锚点定位 - 触发器上使用
anchor-name,叠加层上使用position-anchor(请参阅第 5 节)。
1.2 工作原则
- 使用逻辑属性(
inline-size、block-size、margin-inline、padding-block、inset-inline-start)设置布局尺寸和间距 - 请参阅 CSS:现代架构,了解完整内容。 - 应用“内容优先”与“布局优先”的心智模型:当内容决定布局流向时,使用 Flexbox;当先定义骨架时,使用 Grid。
- 使用
place-*简写形式(place-content、place-items、place-self)在一个声明中沿两个轴对齐。 - 优先使用内在尺寸(
min-content、max-content、fit-content())和弹性轨道(fr、minmax()),而不是固定的width/height,这样可以减少媒体查询,实现更具弹性的布局。 - 请勿使用
overflow: hidden掩盖布局问题,而应从源头解决问题。 例如,某些被替换的元素(如<canvas>、<img>、<svg>、<video>、<iframe>)默认使用display: inline,这可能会在某些布局中增加间隙。请勿使用overflow剪裁间隙,而是通过应用块级 display-outside 值(例如display: block、grid、flex等)来移除间隙。 - 使用
aspect-ratio为媒体预留空间,并防止在素材资源加载之前发生布局偏移。
.sidebar { inline-size: max-content; } /* Size to longest unbreakable token. */
.main-content { inline-size: fit-content; } /* Grow to available space, no further. */
.media { aspect-ratio: 16 / 9; inline-size: 100%; block-size: auto; }
body.centered { display: grid; place-content: center; min-block-size: 100dvb; }
如需了解
calc-size()和具有约束感知功能的固有尺寸,请参阅使用固有尺寸进行计算。
2 Flexbox
一维布局 - 内容沿单个主轴流动,并在交叉轴上对齐。适用于导航栏、工具栏、项目行以及任何单行或单列布局。
正确做法:
- 使用
display: flex建立上下文,并使用flex-direction(row默认值)设置主轴。 - 只要有可能发生溢出,就使用
flex-wrap: wrap。不带overflow: auto/hidden的nowrap会在窄视口中溢出。 - 在紧凑项(例如标签云、过滤条件胶囊、按钮组或导航链接)的换行行上,在
flex-wrap: wrap之后叠加flex-wrap: balance,以便在各行之间均匀分布项,并防止在最后一行出现单个孤立项;不支持的浏览器会回退到wrap。 - 在项上使用
flex简写<grow> <shrink> <basis>(例如flex: 1 1 250px),而不是单独设置flex-grow/flex-shrink/flex-basis。 - 使用
gap(或row-gap/column-gap完整形式)来设置项之间的间距,而不是使用子项外边距。 - 使用
safe(例如align-items: safe center)为位置对齐方式添加前缀,这样当容器比其内容窄时,可聚焦的内容就不会被剪裁。 - 使用
margin-inline-start: auto(或margin-block-start: auto)将单个内容推送到主轴的最远端,这是标准的逃生舱口。 - 使用
align-self替换每个内容的交叉轴对齐。 - 使用
align-items可将所有项在交叉轴上居中;在单个项上使用margin: auto可将其在两个轴上分别居中;仅当容器换行且行间有额外空间时,才使用align-content。 - 在包含长且不可换行内容(网址、代码、长字符串)的 flex 项上设置
min-inline-size: 0(或min-width: 0),因为默认情况下,flex 项不会缩小到小于其内容大小,从而导致溢出。
请勿:
- 请勿在 flex 项上使用
justify-self,它仅适用于网格、块和绝对定位布局。请改用自动外边距。 - 请勿使用
order或flex-direction: *-reverse重新排列互动式内容。它们只会更改视觉顺序,而 DOM 顺序仍会驱动依序焦点,因此键盘 Tab 键流程与用户看到的内容不一致。 - 请勿将
space-around(两端留出半个间距)与space-evenly(在之前、之间和之后留出等距)混淆。 - 别忘了轴翻转:当
flex-direction: column时,justify-content会在块轴上对齐,align-items会在内嵌轴上对齐,这与默认情况相反。 - 请勿将容器及其子项的大小设置为相互填充,这是导致溢出和意外结果的常见原因。为一侧指定确切尺寸。
- 请勿在同一项上同时设置
flex-basis和width/inline-size,因为在 flex 上下文中,flex-basis优先,width会被忽略。使用flex-basis(或flex简写形式)作为调整 flex 项大小的单一事实来源。
.card-grid { display: flex; flex-flow: row wrap; gap: 1rem; }
.card-item { flex: 1 1 250px; } /* grow, shrink, basis */
.card-item-action { margin-inline-start: auto; } /* Push to main-axis end. */
.toolbar { display: flex; align-items: safe center; }
.tag-list { display: flex; flex-wrap: wrap; flex-wrap: balance; gap: 0.5rem; }
3 网格和子网格
二维布局:明确定义行和列,或让引擎自动推导。Subgrid 可让嵌套网格沿用其父级的轨迹,从而使孙级元素在同级元素之间对齐。
选择网格特征:
- 您是否确切知道自己需要多少列?
- 是 - 使用显式轨道(
grid-template-columns: 200px 1fr、repeat(3, 1fr)等) - 不同的列是否需要不同的尺寸(边栏 + 主内容,标题跨越所有列)?→ 使用
grid-template-areas表示已命名的可读区域 - 所有列是否都统一或纯粹按行号定位?→ 使用
repeat(N, ...)或命名的线条 - 否(自适应,商品数量未知)- 使用
repeat(auto-fit, minmax(min, 1fr)) - 最后一行中的内容是否应拉伸以填充剩余空间?→
auto-fit - 最后一行中的空轨道是否应保持其最小尺寸(保留列幽灵槽)?→
auto-fill
- 是 - 使用显式轨道(
- 您是否需要将商品放置在特定位置?
- 是 - 使用
grid-column: <start> / <end>或grid-area: <name> - 否(仅跨多个轨道,流位置无关紧要)- 使用
grid-column: span <n>
- 是 - 使用
- 子元素是否需要沿用父级网格的轨道大小(同级元素之间不规则边缘对齐)?
- 是 - 在受影响的轴上使用子网格
- 每个单元格的子项数量是否可变?→ 子网格仅一个轴;另一个轴使用
grid-auto-rows/grid-auto-columns - 子级数量是否固定?→ 两个轴上都使用子网格也没问题
- 否 - 标准网格,无需子网格
正确做法:
- 使用
display: grid建立上下文。 - 对于复杂的页面级布局,请使用
grid-template-areas。区域名称一目了然,声明可以按行和列对齐,以便于阅读。 - 使用
repeat(auto-fit, minmax(200px, 1fr))可创建自适应卡片网格,拉伸已填充的轨道以填满整行;使用auto-fill可将空的重复轨道保持在最小尺寸。 - 使用
fr可按比例分配轨道,使用minmax(min, max)可创建灵活但有边界的轨道。 - 使用
grid-column: span <n>可跨轨道调整大小,使用grid-column: <start> / <end>可放置在特定行,使用grid-area: <name>可放置在命名区域。 - 使用子网格(
grid-template-columns: subgrid或grid-template-rows: subgrid)解决卡片列表中的“边缘参差不齐”问题,使标题、元数据和 CTA 等内部元素在同级元素之间对齐。 - 将子网格声明与前面的显式
grid-template-rows/-columns声明配对,作为旧版浏览器的同级级联后备。
请勿:
- 不要指望
auto-fit/auto-fill轨道大小来自内容,它来自repeat()大小参数。 - 请勿在互动内容中使用
grid-auto-flow: dense。它能高效地排列内容,但会改变视觉顺序,从而破坏 DOM 顺序的键盘 Tab 键导航流程。 - 当子元素数量可变时,请勿将子网格应用于两个轴。多余的内容会落在最后一个轨道中;请改用
grid-auto-rows/grid-auto-columns作为隐式轴。 - 请勿将
justify-items/align-items(在轨道内对齐项内容)与justify-content/align-content(在容器内对齐网格轨道)混淆。使用错误的键值对不会产生任何效果,也不会发出任何提示。 - 请勿在容器上使用
repeat(auto-fit/auto-fill, ...),而不在容器上使用明确的inline-size。在display: inline-grid或未指定大小的 flex 项中,容器没有可划分的宽度,这会导致轨道数量不可预测。
3.1 代码示例:网格和子网格
页面 shell:<main class="page-layout"> 包含 <header>、<aside>、具有 <div class="card"> 个子元素的 <section class="card-grid">,以及 <footer>。
/* Align grid-template-areas in rows and columns for readability. */
.page-layout {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-areas:
"header header header"
"sidebar main main"
"footer footer footer";
gap: 1.5rem;
}
header { grid-area: header; }
aside { grid-area: sidebar; }
footer { grid-area: footer; }
.card-grid {
grid-area: main;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
grid-template-rows: auto 1fr; /* title block, body block */
gap: 1rem;
}
.card {
grid-row: span 2;
display: grid;
/* Same-cascade fallback: ignored when subgrid is supported. */
grid-template-rows: auto 1fr;
grid-template-rows: subgrid;
}
4 容器查询
查询祖先容器(而非视口)的大小(或计算出的样式)。心智模型:容器查询 = 组件上下文;媒体查询 = 全局页面布局和用户偏好设置(prefers-color-scheme、prefers-reduced-motion)。
正确做法:
- 在封装容器上使用
container-type: inline-size(仅限宽度查询)或container-type: size(两个轴)建立包含上下文,然后才能查询其后代。 - 当嵌套上下文可能发生冲突时,请使用
container-name(或container简写形式:container: card / inline-size)为容器命名。 - 在计算流体类型和间距时,包含容器查询单位:
cqi/cqb(逻辑内嵌/块)、cqw/cqh(物理)、cqmin/cqmax。 - 只要使用
container-type: size,就必须为容器指定明确的block-size,否则后代元素会折叠,因为尺寸包容会强制容器忽略其内容。
请勿:
- 请勿将
block-size用作container-type值,因为该值无效。对两个轴都使用size。 - 声明
container-type后,请勿期望子级的固有尺寸会影响容器。一旦启用包含,系统就会将容器计算为没有子项。 - 请勿依赖不符合条件的祖先元素的后代中的容器查询单位;它们会回退到小视口(
svw/svh)。
4.1 代码示例:使用容器查询单位的流体排版
.card-wrapper {
container: card / inline-size; /* shorthand for container-name / container-type */
}
@container card (inline-size > 400px) {
.content {
display: flex;
gap: 2rem;
}
}
.title {
/* Fluid type bound to the container width, not the viewport. */
font-size: clamp(1rem, 4cqi, 2rem);
}
5 原生重叠式广告、锚点定位和堆叠上下文
何时使用各种叠加层基元:
- 使用
popover实现临时性、非模态界面(弹出式窗口、消息框、提示),这些界面位于顶层,无需z-index管理。 - 将
<dialog>与.showModal()搭配使用,可实现需要焦点捕获和惰性背景的模态互动。 - 请勿在同一元素上同时使用
popover和.showModal(),因为它们是互斥的运行时状态。
锚点定位(叠加层的空间布局):
- 使用
position-area(或anchor(),对于边衬区)和anchor-size(),相对于触发器来定位叠加层并调整其大小。 - 使用
position-try-fallbacks: flip-block(或flip-inline),让浏览器在叠加层超出视口时重新定位。 - 请勿在单个
position-area值中混用物理关键字和逻辑关键字,请选择一种坐标系。 - 使用
@supports (anchor-name: --x)进行功能检测,并提供绝对定位后备方案。
如需了解完整的实现细节、polyfill 策略和
popover值参考,请参阅声明式对话框和弹出式窗口控件以及位置感知提示。如需了解应用于下拉菜单、弹出式菜单和标签页指示器的锚点定位,请参阅视口感知下拉菜单和锚点定位标签页下划线。
6 溢出跟踪和布局稳定性
以可预测的方式管理布局偏移、滚动条和剪裁。
正确做法:
- 使用
overflow: auto,以便仅在内容实际溢出时显示滚动条。 - 使用
overflow: clip剪裁内容,而无需建立滚动容器;使用overflow-clip-margin选择溢出。 - 使用
scrollbar-gutter: stable为滚动条预留空间,并防止内容增加时发生布局偏移。 - 在嵌套滚动区域(例如边栏、模态窗口和列表)上使用
overscroll-behavior: contain(或none),以阻止滚动链冒泡到页面中;或者在中间滚动封装容器(例如滑动显示轨道)上使用overscroll-behavior: chain,以便在不触发本地橡皮筋弹跳的情况下,将多余的滚动传播到祖先滚动容器。 使用
-webkit-line-clamp+display: -webkit-box+-webkit-box-orient: vertical三元组进行多行截断 - 尽管有前缀,但此模式已完全指定,并未弃用。同时声明不带前缀的line-clamp简写形式;尚不支持该属性的浏览器会忽略该属性,不会造成任何危害。 请勿:如果
auto足够,请勿使用overflow: scroll,因为即使没有可滚动的内容,scroll也会强制显示滚动条。如果您只想剪裁,请勿使用
overflow: hidden,因为hidden会建立一个可通过编程方式滚动的滚动容器。
.scrollable-list {
max-block-size: 400px;
overflow-y: auto;
scrollbar-gutter: stable; /* Reserve scrollbar space. */
overscroll-behavior: contain; /* No scroll chaining into the page. */
}
.snippet {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
line-clamp: 3; /* Ignored where unsupported. */
overflow: clip;
}
如需深入了解
overflow: clip和overflow-clip-margin,请参阅溢出剪裁控件。如需了解在滑动显示界面中使用overscroll-behavior: chain进行嵌套滚动切换,请参阅滑动显示。如需了解滚动条颜色、大小和主题,请参阅自定义滚动条的颜色或粗细、深色模式和使滚动条适应高对比度偏好设置。
7 视口机制和轨道分布
- 对于必须考虑浏览器界面变化(网址栏收起/展开)的移动版布局容器,请使用
dvh/dvw。 - 请勿使用
100vw实现全宽布局,因为它会忽略滚动条宽度并导致水平溢出。请改用100%、100dvw或100svw。
如需了解自适应设计切入点(动态视口单位、为媒体预留空间、自适应排版),请参阅自适应设计。
8 网格通道(也称为瀑布流)
该规范正在开发中,名称为“网格车道”(例如 display: grid-lanes),而早期的实验性原型使用的是 grid-template-rows: masonry。
正确做法:
- 现在,您可以将网格与
grid-auto-flow: dense搭配使用,以实现紧密排列,同时接受 DOM 顺序可能与视觉顺序不一致。 - 如果列表项是文档片段而非权重相同的卡片,请使用多列 (
columns: 3; column-gap: 1rem) 布局来呈现内容丰富的瀑布流。 - 仅将
grid-template-rows: masonry视为渐进增强,使用@supports进行功能检测。
请勿:
- 在您的基准目标赶上之前,请勿将
grid-template-rows: masonry作为硬性要求发布。
.gallery { columns: 3 200px; column-gap: 1rem; }
.gallery > * { break-inside: avoid; margin-block-end: 1rem; }
@supports (grid-template-rows: masonry) {
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
grid-template-rows: masonry;
gap: 1rem;
columns: unset;
}
}