CSS 布局让人困惑,通常不是因为属性太多,而是因为我们还没判断浏览器正在处理哪一种布局。同样写 width: auto,块盒通常会占满包含块的可用宽度,行内块则会根据内容收缩;同样写 margin,块轴上的相邻外边距可能折叠,行内轴上的外边距不会。把这些现象放回视觉格式化模型里,规则就能连起来了。
我们从盒模型开始,逐步看包含块、display、逻辑尺寸、两个轴上的尺寸计算,以及文字行中的基线和行盒。重点不在记住每个结论,而在于弄清浏览器用什么参照、按什么顺序计算。这样遇到布局偏差时,你能沿着规则找到原因。
下面默认讨论正常流。浮动、绝对定位、Flex 和 Grid 各有自己的布局规则,但仍会用到盒模型、包含块、逻辑轴和行内排版这些基础概念。
页面中的元素会生成一个或多个矩形盒。普通盒从内到外分为四层:内容区、内边距、边框和外边距。背景默认延伸到边框区域之下,外边距保持透明。内边距和边框不能取负值,外边距可以。outline 画在盒子外侧,但不参与尺寸计算。

先把后面会反复用到的术语说清楚:
p 和 span。img 和多数表单控件。html,视口或打印页面区域会建立初始包含块。逻辑方向比“上下左右”更能适应不同的书写模式。margin-block-start 表示块轴起点一侧的外边距:横排时通常对应 margin-top,竖排时可能对应左侧或右侧。组件需要兼容多种书写模式时,优先写逻辑属性。
每个盒都要相对于某个包含块计算尺寸和位置。正常流中的块盒,通常把最近一个能生成块盒或列表项盒的祖先内容区当作包含块。例如,article 里的 p 通常以 article 的内容区计算百分比宽度和可用行内尺寸。
<body>
<main>
<article>
<p>我的可用宽度通常由 article 的内容区决定。</p>
</article>
</main>
</body>这条依赖链会逐级向上延伸:p → article → main → body → html → 初始包含块。初始包含块的尺寸由视口或打印页面区域决定,与根元素里的内容有多长无关。因此,视口单位、固定定位和页面内容高度不能混为一谈。
看到百分比,先问它相对于谁。inline-size: 60% 通常以包含块的行内尺寸为基准。正常流元素若使用百分比块尺寸,包含块在对应轴上还得有可解析的明确尺寸。调试时如果只盯着百分号,很容易找错方向。
display:外部角色与内部布局display 并非只有“块”和“行内”两种选择。可以把它拆成两层来看:
block 和 inline。flow、flow-root、flex、grid 和 table。.card { display: block flow; }
.badge { display: inline flow; }
.chip { display: inline flow-root; } /* 等价于传统 inline-block */链接设为 display: block 后,会在正常流中独占一行,整块区域也都可以点击。列表项改成 inline 后,标记盒通常不会再按原来的方式出现。CSS 改变的是显示角色,HTML 语义和合法的嵌套关系不会随之改变,所以视觉需求仍应建立在合适的文档结构上。
下面几个值看起来相近,实际影响不同:
none:不生成盒,不占布局空间,通常也会从无障碍树中移除。contents:元素自身不生成盒,子元素直接到外层参与排版;这个元素的边框、背景和外边距都不会绘制。flow-root:建立新的块格式化上下文,可以包住浮动,也会阻止某些外边距穿过边界。list-item:生成主体盒和标记盒,标记可以放在内容外侧或内侧。改变 table、列表等语义明确的元素的显示角色,可能影响辅助技术识别结构。如果一个视觉方案需要大量 ARIA 才能补回原有语义,先检查 HTML 结构是否选对了。
inline-size 和 block-size 分别控制行内轴与块轴上的尺寸,会随着书写模式转向。物理属性 width、height 始终对应水平和垂直方向。编写需要适应不同语言排版的组件时,逻辑尺寸通常更贴近设计意图。

.article {
inline-size: min(68ch, 100%);
block-size: auto;
}
.vertical-note {
writing-mode: vertical-rl;
inline-size: 20ch; /* 此时控制竖直方向的一行长度 */
}max-content 取内容不主动换行时所需的尺寸,长文本可能因此冲出容器。min-content 把内容压到无需强行拆开不可断片段的最小尺寸。对普通文本来说,它通常接近最长单词或最长不可断片段的宽度。fit-content 会在可用空间、最小内容尺寸和最大内容尺寸之间取值。空间小时可以收缩,空间很大时也不会无限伸展。fit-content 与简单的 width: auto 计算方式不同。它同时考虑内容的固有下限、自然上限和当前可用空间,适合标签、浮层、工具条这类需要跟随内容、又不能撑破容器的组件。
min-inline-size、max-inline-size、min-block-size 和 max-block-size 为尺寸限定上下界。约束发生冲突时,最小值通常优先于最大值。响应式组件可以让首选尺寸随容器变化,同时保留合理范围:
.panel {
inline-size: 70%;
min-inline-size: 18rem;
max-inline-size: 60rem;
}物理属性 min-width、max-width、min-height 和 max-height 解决的是同类问题,只是方向不随书写模式改变。当 width 与 inline-size 落在同一物理轴上时,优先级更高或位置靠后的声明生效。组件里最好选定一套方向表达,避免无意覆盖。
aspect-ratioaspect-ratio 在两个轴之间建立比例。当一个轴可以确定、另一个轴使用自动尺寸时,浏览器会按比例推导自动轴。图片等替换元素通常自带固有比例;普通 div 也能通过显式比例提前占位,减少媒体加载后的页面跳动。
.video-cover {
inline-size: 100%;
block-size: auto;
aspect-ratio: 16 / 9;
object-fit: cover;
}比例应用在哪一层盒上,由 box-sizing 决定。使用 border-box 时,目标比例对应边框盒;使用默认的 content-box 时,则对应内容盒。
box-sizing:声明尺寸量到哪里默认的 content-box 让 width、height 或逻辑尺寸只计算内容区,内边距和边框继续加在声明尺寸之外。border-box 会把内容、内边距和边框一起算进声明尺寸。
.content-box {
box-sizing: content-box;
width: 240px;
padding: 20px;
border: 4px solid;
/* 边框盒宽度 = 240 + 40 + 8 = 288px */
}
.border-box {
box-sizing: border-box;
width: 240px;
padding: 20px;
项目中常会统一设置:
*, *::before, *::after { box-sizing: border-box; }border-box 不能保证盒子永远不溢出。声明宽度超过包含块,或内容中存在不可断的长字符串时,内容照样可能越界。它解决的是声明尺寸包含哪些部分,让组件尺寸更容易计算。
正常流中的块盒通常不需要固定高度。使用 block-size: auto 时,浏览器会根据内容、子元素和盒属性算出实际尺寸。如果父元素在块轴上没有边框或内边距,第一个或最后一个子块的外边距可能与父元素外边距折叠,于是看起来像是外边距跑到了父元素外面。加上边框或内边距,或者建立新的块格式化上下文,都可以隔开这两组外边距。
正常流块盒要使用百分比块尺寸,包含块在对应轴上通常得有明确的非 auto 尺寸:
.known { block-size: 400px; }
.known > .half { block-size: 50%; } /* 200px */
.unknown { block-size: auto; }
.unknown > .half { block-size: 50%; } /* 通常按 auto 处理 */这也是 height: 100% 经常没有效果的原因。父级高度由内容决定时,子级不能先拿父级高度计算百分比,否则两者会互相依赖。你可以明确祖先的高度链,也可以根据布局目标改用 min-block-size: 100vh、Flex 或 Grid。
overflow 的常用值如下:
visible 是默认值。内容可以画到盒外,但盒子的几何尺寸不会因此扩大。hidden 会裁剪内容。盒仍可能成为滚动容器,只是通常不显示滚动条。clip 直接在溢出裁剪边界截断内容,也不提供滚动访问。scroll 无论内容是否溢出都会建立滚动机制,可能一直保留滚动条。auto 在需要时提供滚动机制,常用于内容容器。overflow 可以写两个值,依次对应 x 轴和 y 轴,也可以分开写成 overflow-x 与 overflow-y。这里的 x、y 是物理轴,不会随书写模式旋转。一个轴设置为非 visible 或非 clip 的值时,另一个轴的计算结果可能随之变化,实际项目中要在目标浏览器里验证。

相邻正常流块盒在块轴上的外边距可能折叠。两个值都为正时,结果取较大的那个,不会相加。有负值参与时,先找出最大的正外边距,再减去绝对值最大的负外边距;如果全部为负,就取最负的值。边框和内边距不参与折叠。
.first { margin-block-end: 24px; }
.second { margin-block-start: 40px; }
/* 两盒间距通常为 40px,不是 64px */负的块起点外边距会把元素自身和后续的正常流内容向前拉,负的块终点外边距则会让后续元素提前开始。元素重叠后通常仍按文档顺序绘制,后出现的正常流内容可能盖住前面的内容。使用负外边距前,要把可能出现的遮挡一起算进去。
auto 与百分比正常流中的非替换块盒,在行内轴上要满足下面这条尺寸关系:
起点外边距 + 起点边框 + 起点内边距 + 行内尺寸
+ 终点内边距 + 终点边框 + 终点外边距
= 包含块的内容行内尺寸这七项里,只有 inline-size、margin-inline-start 和 margin-inline-end 可以使用 auto。内边距与边框不能靠 auto 分配剩余空间。
auto 如何分配剩余空间auto 时,这一项吸收剩余空间,让方程成立。auto 且尺寸明确时,剩余空间会被平分,元素因此沿行内轴居中。auto 时,自动外边距通常先按零计算,自动尺寸再填满剩余空间。auto 时,两侧外边距按零计算,尺寸尽量占满可用空间。auto,且总和无法满足方程时,就会形成过度约束。在常见的从左到右场景中,浏览器会调整行内终点外边距来平衡方程。.centered {
inline-size: 20rem;
margin-inline: auto;
}显式尺寸大于包含块时,为了让方程成立,自动外边距甚至可能算出负值。负的行内外边距也能让子元素伸出父内容区,但由此产生的溢出和遮挡需要单独处理。
行内尺寸以及行内方向上的内边距、外边距百分比,通常都以包含块的行内尺寸为基准。百分比和 em、px 混在一起时,剩余空间不容易心算。可以使用 calc()、min()、max() 或 clamp(),把单位之间的关系直接写出来。
.mixed {
inline-size: calc(100% - 4rem);
margin-inline: auto;
}边框宽度不能使用百分比。即使组件需要随容器伸缩,也不要只靠几组百分比把总和凑成 100%;边框、滚动条和最小内容尺寸都会改变最后的计算结果。
浏览器排一个段落时,会先形成连续的行内内容,再根据可用的行内尺寸把内容断成多行,并沿块轴依次放置。text-align 决定每一行在包含块中的对齐方式;使用 justify 时,浏览器会调整词间或字符间的空白,让末行之外的各行尽量铺满。

要理解行高,需要分清下面五个概念:
font-size 决定它的标称高度。line-height。对非替换行内内容:
行距 = line-height - font-size
半行距 = 行距 / 2例如,font-size: 16px; line-height: 24px 会得到 8px 的总行距,上下各分到 4px。如果 line-height 小于 font-size,半行距会变成负值,字形可能伸出行内盒,视觉上与相邻行重叠。
line-height 写在块元素上时,实际影响的是其中的行内内容,同时为行盒建立最小高度。正文通常适合使用无单位数值:
body { line-height: 1.6; }
h1 { font-size: 2rem; }
small { font-size: 0.8em; }无单位的 1.6 会以比例形式继承,每个后代都用自己的 font-size 乘以 1.6。若写成 1.6em,父元素会先算出固定使用值,再把这个值传给后代;后代字号变化后,行高可能不再协调。
vertical-align、行内装饰与断行片段行内盒默认按基线对齐。vertical-align 可以移动行内盒,移动后的盒如果超出当前行盒范围,还会把整条行盒撑高:
top / bottom:让行内盒的块起点或终点与整条行盒的对应边缘对齐。text-top / text-bottom:与父元素内容区的对应边缘对齐。middle:让元素中点对齐到父元素基线上方约 0.5ex 的位置,并非容器几何中心。super / sub:按浏览器的排版策略上移或下移。line-height 为基准计算位移。给 span 增加块轴内边距、边框或外边距,会扩大装饰的绘制区域,但不会增加非替换行内盒的行高。内边距过大时,背景可能盖住相邻行。行内起止方向上的内边距、边框和外边距会占用行内空间,因此也会改变断行位置。
跨行的行内元素默认使用 box-decoration-break: slice,效果近似于先画一个长盒,再按行切成几片。改成 clone 后,每个行片段都会拥有完整的内边距、边框、圆角和背景原点。
.highlight {
padding-inline: .35em;
border: 2px solid #f08a5d;
border-radius: .4em;
box-decoration-break: clone;
-webkit-box-decoration-break: clone;
}实际字形不一定与 em 盒等高。即使设置了 line-height: 1,背景和字形仍可能显得拥挤。调试这类排版时,要使用目标字体、字号和运行平台观察实际效果,不能只看计算值。
inline-block替换元素可能带有固有尺寸或固有宽高比。块级 img 使用 inline-size: auto 时,通常会取图片的固有宽度。只明确一个轴时,另一个轴会按照固有比例缩放,除非它也被显式指定。
行内替换元素的内容、内边距、边框和外边距会共同形成行内盒,因此都可能影响行盒高度。图片默认把外边距盒的块终点边缘放在基线上。基线下方还要给文字的下行部留出空间,所以行内图片底部常会出现一条空隙。
常用处理方式:
/* 图片不需要参与文字基线排版 */
.media img { display: block; }
/* 图片需要与文字同行 */
.icon { vertical-align: middle; }图片本身的 line-height 不决定图片盒高度,但百分比形式的 vertical-align 仍会使用这个值计算位移。负的块终点外边距可以把行内替换元素向下拉,也可能让它侵入下一行,设置后要检查相邻内容。
inline-block 同时有行内和块级特征。对外,它像一个不可断开的行内替换盒,会参与基线对齐;对内,它建立块级格式化环境,可以使用宽高、内外边距和 text-align。没有指定行内尺寸时,它通常根据内容收缩,不会像普通块盒那样自动填满父级。
列表项还会生成标记盒。list-style-position: outside 把标记放在内容区外侧;inside 则让标记成为开头的行内内容,参与首行排版。列表项本身仍可以为后代建立包含块。
flow-root、contents 与可见性传统的单关键字 block、inline 和 inline-block,可以分别理解为 block flow、inline flow 和 inline flow-root。两关键字语法直接写明盒在外部怎样参与布局、内部又采用什么布局方式。
display: flow-root 会建立独立的块格式化上下文。容器因此能包住内部浮动,并隔开部分内外布局影响。用它处理浮动时,不会像 overflow: hidden 那样顺带裁掉原本需要显示的溢出内容。
display: contents 会移除元素自身的布局盒,子元素仍继续参与排版。它有时能绕开网格中的层级限制,但父元素的背景、边框、尺寸和点击区域也会一起消失。使用前还要验证目标浏览器中的无障碍树表现。
visibility 与 display 的区别visibility: hidden 会隐藏元素,但保留原有布局空间。这个属性可以继承,后代也能显式改回 visible。display: none 不生成布局盒,也不保留空间。visibility: collapse 在表格行列和部分布局上下文中有专门的折叠行为,用在普通元素上通常接近 hidden。visibility 是离散变化,单独使用无法产生渐隐。实现淡入淡出时,可以让 opacity 负责连续动画,再用 visibility 在动画端点控制命中测试和可访问状态。只把透明度设为 0,并不会让元素从页面交互中消失。
.popover {
opacity: 0;
visibility: hidden;
transition: opacity .2s ease, visibility 0s linear .2s;
}
.popover.is-open {
opacity: 1;
visibility: visible;
transition-delay: 0s;
}盒子尺寸或文字排版出现偏差时,可以按下面的顺序检查。这样能先找到计算依据,减少反复追加覆盖声明的情况。
先确认元素生成了什么盒。查看 display 的外部和内部角色,判断它是块盒、行内盒、行内块、替换元素,还是没有生成盒。
找到包含块并确认书写模式。明确百分比、auto 和逻辑属性参照的是哪条轴、哪个尺寸,不要默认它们都相对于视口或物理宽高。
写出尺寸方程。检查 box-sizing、内容尺寸、内边距、边框、外边距和 min/max 约束,确认剩余空间由哪一项吸收。
检查块轴上的特殊情况:百分比高度有没有明确参照,内容是否溢出,外边距是否折叠,负外边距是否造成重叠。
排查视觉格式化问题时,我们可以把浏览器的工作归纳为三步:确定盒的角色和参照系,沿两个逻辑轴计算尺寸,再把行内盒组织成行盒。把页面现象还原到这三步,通常就能找到可以验证的原因。
我们最后把排查思路收拢一下。元素生成的盒由内容、内边距、边框和外边距组成,box-sizing 决定声明尺寸量到哪一层;包含块提供尺寸和位置的参照,逻辑轴则让同一套规则适应不同书写模式。
接着看元素怎样参与布局。display 同时说明盒的外部角色和内部布局,none、contents 与 flow-root 会产生不同结果。块轴上要留意自动尺寸、百分比依赖、溢出和外边距折叠;行内轴上的七项方程则说明了 auto 怎样吸收剩余空间,也解释了双侧自动外边距为何能实现居中。
文字排版还要继续看行盒。line-height、半行距、基线和 vertical-align 会共同决定一行的实际高度。非替换行内元素的块轴装饰不会撑高行盒,替换元素和 inline-block 的外部盒却会参与行盒计算。控制显示状态时,也要记住 visibility: hidden 会保留空间,display: none 不生成盒;连续的淡入淡出应由 opacity 完成。
如果问题出现在文字行内,就检查 font-size、无单位 line-height、基线、vertical-align、替换元素尺寸和跨行装饰。只看元素的 height 往往找不到原因。
最后检查可见性和语义。分清 none、contents、hidden 与 opacity,并确认显示角色的改变没有破坏可访问结构。