一个盒子为什么会“跑到旁边”?文字为什么能绕开图片?按钮又为什么能一直停在视口角落?这些现象看起来都像元素被挪了位置,背后用到的布局规则却不一样。浮动改变的是行框的绕排方式,定位还会牵涉参照系、占位和层叠顺序。如果只记住几个属性值,碰到父容器高度塌陷、绝对定位跑偏或 sticky 不吸顶,往往还是不知道该从哪里查起。
我们从浏览器怎样放置盒子讲起,再依次看 float、clear、五种 position、包含块、偏移、尺寸计算、层叠上下文和溢出。读完之后,你应该能顺着浏览器的计算过程判断位置,而不是靠反复试属性碰运气。
页面的主结构通常更适合交给 Flexbox 或 Grid。浮动可以继续用来做图文环绕,定位则适合角标、弹层、吸顶标题和固定操作区。先判断需求属于哪种布局,再决定用什么属性,会少走很多弯路。
浏览器默认按照普通流排版:块盒沿块轴依次排列,行内盒进入行框,再沿行轴排列。遇到位置不对的情况,我们先回答三个问题:
float 的情况比较特别。浮动盒不再像普通块盒那样占满一行,却仍会挤压后续行框,所以文字会绕开它。relative 保留原来的位置,只改变元素画在哪里。absolute 和 fixed 会离开普通流,后续内容会补上它们留下的空位。sticky 一开始照常参与普通流,滚动到设定的边界后才进入吸附状态。
float 常用的值有 left、right、inline-start、inline-end 和 none。left、right 对应固定的物理方向;两个逻辑值会跟着书写方向变化。如果界面要同时支持从左到右和从右到左的语言,逻辑值通常更合适。
.article-figure {
float: inline-start;
width: min(42%, 18rem);
margin: 0 1rem 0.75rem 0;
}
@media (max-width: 36rem) {
.article-figure {
float: none;
width: 100%;
margin: 0 0 1rem;
}
}任何元素都可以浮动。一个原本处于行内的元素设置浮动后,也会生成块级盒,再参与浮动布局。浮动盒的外边距不会和相邻外边距折叠,所以两个盒子各有 25px 外边距时,中间可能会留下 50px。float: none 除了是默认值,也可以在响应式断点里撤销前面设置的浮动。

浮动元素通常以最近的块级祖先为包含块。浮动盒向指定的一侧靠拢,旁边的行框会缩短可用宽度来避让它。这里容易看错:块盒的背景和边框仍可能延伸到浮动盒下面,真正被挤开的主要是行内内容和行框。
浮动并不是把盒子随意“飘”到一边。浏览器放置它时要同时满足一组约束。下面九条看起来不少,但在复杂图文环绕里很有用:
源码顺序会直接影响最后的位置。例如,把两个宽度都是 300px 的盒子放进 500px 宽的容器,一个向左浮动,一个向右浮动。浏览器不会让它们重叠 100px,而是把后出现的盒子向下放,直到这一行能容纳它。
负外边距很容易制造误判。浮动规则约束的是盒子的外边缘,负外边距却能让内容区越过这条边,于是视觉上就像浮动穿出了父容器。让元素向侧边少量“悬挂”还算容易控制;用负外边距向上侵入已经排好的内容,可能直接盖住文字,不适合用来搭建稳定布局。
浮动通常不像定位那样容易互相覆盖,不过负外边距和超宽内容仍可能造成重叠。这时要把块级盒和行内盒分开看:
你可能见过这样的效果:标题的彩色背景像是“钻到”图片下面,标题文字却仍然避开图片,甚至画在图片上方。标题本身并没有变窄,它的块盒仍然横跨父容器,只是内部行框在避让浮动。
看到父元素的背景,不代表它已经包含了浮动子元素。如果父盒里只有浮动子项,它在普通流中的高度仍可能是零。画面上的背景也可能来自内边距、固定高度或后续内容。
clear 用来规定块级元素的哪一侧不能再出现前面的浮动。clear: left 只避开左浮动,right 只避开右浮动,both 会同时避开两侧。需要适配不同书写方向时,可以使用 inline-start 和 inline-end。
.next-section {
clear: both;
}浏览器会在被清除元素上方补出必要的清除距离,把它的上边框推到相关浮动的外边缘以下。清除距离还要和元素原有的上外边距一起计算,所以给标题增加 margin-top,不一定能得到你预想的间隔。如果只是想让浮动图片和下一段之间多一点距离,给浮动元素设置下外边距通常更直接,因为这条外边缘本来就要被避开。
如果我们要解决的是父容器高度塌陷,可以选下面两种方式:
/* 现代做法:创建新的块级格式化上下文 */
.media-object {
display: flow-root;
}
/* 兼容旧项目的 clearfix */
.clearfix::after {
content: "";
display: table;
clear: both;
}overflow: auto 或 hidden 也可能让父元素包含浮动,但它们还会引入滚动条或裁切。需求只是包含浮动时,display: flow-root 更容易让后来读代码的人看懂。

position 的初始值是 static。区分五种模式时,不妨先看两个问题:原来的占位还在不在,偏移又是相对谁计算的。
static 元素会忽略常规的定位偏移。relative 先在普通流中得到静态位置,再按偏移移动,旁边的元素不会跟着重排。absolute 会生成块级盒,原来的空位也会闭合。fixed 和绝对定位相似,但通常以视口为包含块;祖先上的变换等属性可能改变这个参照环境,因此排查时别忘了向上检查。sticky 仍在普通流中,只是在滚动到边界后受到额外约束。
可以把包含块理解成一套坐标系:浏览器用它计算位置和百分比尺寸,但它不负责保证元素留在框内。偏移足够大时,元素完全可以跑到包含块外面。
对 static 或 relative 的非根元素来说,包含块通常来自最近的块级祖先、表格单元格或行内块祖先的内容区。分析绝对定位时,关键是找到最近一个 position 不为 static 的祖先。
.card {
position: relative; /* 只为角标建立局部坐标系 */
}
.card__badge {
position: absolute;
inset-block-start: 0.5rem;
inset-inline-end: 0.5rem;
}
给父级写 position: relative,但不设置偏移,是给绝对定位子元素建立局部参照的常见做法。绝对定位元素也能继续为自己的绝对定位后代建立包含块,所以坐标系可以一层层嵌套下去。
物理偏移包括 top、right、bottom、left。对应的逻辑属性是 inset-block-start、inset-block-end、inset-inline-start、inset-inline-end,它们会跟着书写模式改变方向。
理解偏移时,先盯住属性名字里的那条边。right: 2rem 的意思是,元素右侧的外边距边缘和包含块右边相距 2rem,并不是从左上角向右移动 2rem。正值通常把元素推向包含块内部,负值则向外延伸。使用百分比时,物理方向的上下偏移参照包含块高度,左右偏移参照宽度;逻辑属性参照各自轴向的尺寸。
/* 四个物理边:上、右、下、左 */
.panel { inset: 1rem 2rem 3rem 4rem; }
/* 一个值:两端相同;两个值:起点、终点 */
.panel {
inset-block: 1rem 2rem;
inset-inline: 3rem auto;
}inset 是四个物理边的简写,顺序和 margin 一样,都是上、右、下、左。它不是四个逻辑属性的简写。比如 inset: 20px 2em 表示上下为 20px,左右为 2em。至于 auto 最终怎样计算,要看定位模式和同一轴上的其他条件,不能直接当成零。
如果绝对定位元素的 width、height 都是 auto,而同一轴两端的偏移已经确定,浏览器就能用两端距离反推出这一轴的尺寸:
/* 填满包含块的左半边 */
.left-half {
position: absolute;
inset: 0 50% 0 0;
}这时如果又写上 width: 50%; height: 100%,再叠加内边距和边框,默认的 content-box 会把额外尺寸加到内容宽高之外,元素就可能溢出。可以删掉重复的宽高约束,或者改用 box-sizing: border-box。
min-width、min-height、max-width、max-height 会继续限制最后采用的尺寸,而且这些值不能为负。最小尺寸和最大尺寸冲突时,最小尺寸通常优先。做弹层时,尽量别同时锁死四边、固定宽高和最小尺寸。给某个方向留出 auto,内容增长或视口缩小时才有调整余地。
.dialog {
position: absolute;
inset: 10% 1rem auto 50%;
width: min(32rem, calc(100% - 2rem));
max-height: 80%;
overflow: auto;
transform: translateX(
绝对定位的非替换元素在水平方向上要满足下面这条方程:
left + margin-left + border-left + padding-left + width
+ padding-right + border-right + margin-right + right
= 包含块宽度垂直方向也有一条结构相同的方程。已知项相加之后,剩余空间要由某个值为 auto 的项目吸收。我们可以据此判断几种常见情况:
left、width、right 都为 auto 时,元素从静态位置附近开始,并按内容收缩适配。auto 时,两边外边距均分剩余空间,可实现水平居中。auto 时,它吸收方程的剩余量。right,从右到左语言通常重算 left。bottom,不会牺牲 top。.centered {
position: absolute;
inset: 0 1rem;
width: 20rem;
height: 8rem;
margin: auto; /* 四边偏移明确时,可水平并垂直居中 */
}再强调一次,偏移里的 auto 不等于 0。约束较少时,top: auto 或行内起点侧的 auto 可能采用元素原本在普通流中的静态位置。不过元素已经脱离普通流,旧位置不会继续占着空间,它仍可能和后续文字重叠。
图片这类替换元素还有固有宽高。它们的 width: auto 通常先取固有宽度,height: auto 先取固有高度,不会走普通非替换元素那套按内容收缩的过程。浏览器随后再计算静态位置、自动外边距和剩余偏移。横向过度约束时,仍会重算行末一侧;纵向则重算 bottom。
相对定位以元素的静态位置为起点计算偏移。top: 10px 会把盒子向下挪,left: -20px 会把它向左挪。其他元素仍按它没有移动时的位置排版,因此原处可能留下空白,移动后的盒子也可能盖住邻居。
同一轴的两端都设置数值时,就会出现过度约束。纵向以 top 为准,bottom 按 -top 处理;从左到右书写时,横向以 left 为准,right 按 -left 处理,从右到左时顺序相反。相对定位元素还会为绝对定位后代建立包含块。
固定定位会完全脱离普通流,通常相对视口计算位置。页头、侧栏或底栏固定后不会随文档滚动,也不会主动给正文腾出空间。我们需要在正文上设置相应的 padding,或者用网格区域和独立滚动容器预留位置。
.toolbar {
position: fixed;
inset: auto 1rem 1rem auto;
z-index: 20;
}到了移动端,还要检查安全区、软键盘和动态视口带来的变化。如果整套页面框架都要固定,Grid 往往更好控制。fixed 更适合那些确实要一直贴住视口的操作入口。
sticky 元素起初和普通元素一样参与排版。指定的边到达粘性约束边界时,它开始吸附;反向滚动回到原位置,吸附也会解除。相关方向至少要有一个偏移不为 auto,常见写法是 top: 0。
.scroll-panel {
max-height: 24rem;
overflow: auto;
}
.section-title {
position: sticky;
top: 0;
z-index: 1;
background: white;
}最近一个具有滚动机制的祖先会影响粘性约束。因此,元素没有相对视口吸顶时,先向上检查是否出现了 overflow: auto | scroll | hidden。粘性元素会保留原占位。多个吸顶元素到达同一个位置后可能互相叠住,源码靠后的元素通常画在前面;可以给它们明确设置 z-index,并用不同的 top 值错开层次。若多个方向的偏移都不是 auto,这些边都会参与粘性约束。

z-index 只负责同一个层叠上下文里的前后顺序。数值可以是正数、零或负数,也不需要连续。数值越大,元素在当前上下文中越靠前,但这套顺序并不能跨越整个页面。
元素建立自己的层叠上下文后,后代只能在这个局部范围里排序。即使子元素写成 z-index: 999999,它也越不过父上下文,无法盖住处于更高外部上下文的兄弟元素。排查遮挡问题时,先找到两个元素最近的共同层叠上下文,再比较它们在这一层的顺序。继续给深层子元素加大数字通常没有用。
.modal-layer {
position: fixed;
inset: 0;
z-index: 100;
}
.card {
position: relative;
z-index: 0; /* 建立局部分组时要理解其边界 */
}z-index: auto 通常使用当前上下文的默认层级,一般不会创建新的层叠上下文。定位元素使用显式整数时,则会形成局部层叠上下文。Flex 和 Grid 项即使没有设置定位,也能使用 z-index。
定位问题经常和溢出一起出现。绝对定位元素可以跑出包含块,超出的部分能不能看见,要看祖先的 overflow。visible 允许内容画到边界外,hidden 会裁切,auto 和 scroll 会建立滚动机制。溢出设置还可能改变 sticky 的滚动参照,所以不要为了“清理”布局随手加上 overflow: hidden。
误区一:浮动等于完全脱流。 浮动不再像普通块盒那样占一整行,却仍然影响后续行框。绝对定位才会让后续内容按它不存在的方式排版。
误区二:clear 应该写在浮动元素上。 clear 通常写给需要下移避让的后续块盒。要让父容器包含浮动,更适合用 flow-root 或 clearfix。
误区三:所有偏移都从左上角开始计算。 属性名已经说明了起点:top 从上边量,right 从右边量,bottom 从下边量,left 从左边量。
误区四:绝对定位永远相对直接父元素。 它会寻找最近的定位祖先。直接父元素仍为 static 时,搜索还会继续向上。
误区五:z-index 的数字足够大就能盖住所有内容。 局部层叠上下文会把子元素限制在父级所在的层次里。
误区六:sticky 就是滚动后的 fixed。 sticky 始终受滚动容器和自身普通流范围约束,原来的占位也一直保留。
实际调试时,可以按下面的顺序往下查:
准备用浮动时,先确认需求确实是文字环绕。浮动元素会生成块级盒,外边距不折叠,旁边的行框会避让它,而父容器未必会算入它的高度。明确这些影响后,再决定下一段怎样清除浮动。
准备使用定位时,先问原来的占位要不要保留,再去找包含块。relative 保留占位,也能建立定位参照;absolute 脱离普通流并寻找定位祖先;fixed 通常贴着视口;sticky 则同时受滚动容器和非 auto 偏移约束。
遇到尺寸或遮挡问题,把偏移、宽高和内外边距放回同一条方程里核对。再用 min/max 与 box-sizing 控制边界,按局部层叠上下文理解 z-index,最后检查 overflow 有没有裁切内容,或者悄悄改变 sticky 的参照。沿着这个顺序排查,通常比继续叠加属性更快找到原因。