第 07 节 · 定位与层叠
第 07 节 · 定位与层叠
📌 版本信息:基于 CSS Position Module Level 3(
position: sticky已全浏览器稳定;2026-09-06 核对) 📚 来源:MDN 定位 | MDN z-index
当地图控件需要浮在地图上
地图页面只靠文档流和 Flex 还不够:缩放按钮要贴在地图右上角,比例尺要贴左下角,页面滚动时顶栏不能离开视口,点中要素后信息窗要压在地图上。这些控件和地图内容重叠,却不能占用地图的布局空间。
第 05、06 节解决的问题是“元素在文档流里怎么排队”,这一节解决的是“元素怎么脱离文档流、以谁为参照、谁压在谁上面”。地图应用里几乎每一帧界面都依赖这套能力,与其在后面的地图模块里被坑,不如先把四个 position 值和层叠规则钉死。
这一节讲什么
本节讲 relative、absolute、fixed、sticky 四种定位方式,定位参照物的判断规则,以及 z-index 和层叠上下文。学完这一节可以做到:
- 说清四种定位分别占不占文档流位置、相对谁定位
- 掌握“子绝父相”,把控件贴到任意容器的任意角落
- 用
fixed做固定顶栏,用sticky做吸顶表头 - 用
z-index按约定分层,并排查“写了 9999 却不生效”的问题 - 独立完成假地图上的控件、角标、弹窗定位
文档流之外还有一套坐标
常规文档流里,块级元素从上往下堆,行内元素从左往右排,元素的位置由前后关系自动决定。定位则是在这之外给元素发一套显式坐标,四个值的行为各不相同:
| 值 | 占文档流位置 | 相对谁定位 | 典型用途 |
|---|---|---|---|
relative |
占 | 自己原来的位置 | 微调位置、给 absolute 子元素当参照 |
absolute |
不占 | 最近的定位祖先 | 地图角落控件、消息角标、弹窗 |
fixed |
不占 | 视口(默认) | 固定顶栏、回到顶部、悬浮球 |
sticky |
占 | 最近的滚动容器 | 吸顶表头、章节目录 |
定位元素的 top、right、bottom、left 决定偏移方向。写定位之前先回答两个问题:它离开文档流了吗?它相对谁算坐标?两个问题答错一个,控件就会飞到地图外面。
relative 与 absolute:子绝父相
relative 单独用的场景不多,它真正的价值是当参照物。absolute 子元素往上找祖先,找到第一个 position 不是 static 的就以它为参照;一个都没有就相对初始包含块(约等于整个页面)定位:
.map { position: relative; } /* 父相:留在文档流里,同时成为参照 */
.zoom-tools {
position: absolute; /* 子绝:离开文档流 */
top: 12px;
right: 12px; /* 相对 .map 的右上角 */
}
为什么父元素用 relative 而不是 absolute?因为 absolute 会让父元素也离开文档流,页面其他内容可能塌陷;relative 保持父元素原位占位,只是多了一个“定位锚点”的身份。这个组合就是口诀“子绝父相”,地图缩放控件、图层切换按钮、消息角标都是它的直接应用。
fixed 与 sticky:钉住与吸住
fixed 默认相对视口定位,页面怎么滚它都不动:
.topbar {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 56px;
z-index: 100;
}
body { padding-top: 56px; } /* 内容区补出顶栏的高度,否则首屏被盖住 */
sticky 平时还在文档流里,滚到设定阈值后才钉住。它钉在最近的滚动容器内,而不是一直钉在视口:
th {
position: sticky;
top: 0;
background: #fafafa; /* 不加背景,滚动内容会从表头下面透出来 */
}
fixed 是“从头到尾钉死”,sticky 是“先随流滚动,到边界才粘住”。吸顶表头、侧边目录这类需求用 sticky,页面整体顶栏用 fixed。
z-index 与层叠上下文
元素重叠时默认后写的盖住先写的。要主动改变顺序,给定位元素或 Flex/Grid 子项写 z-index;普通 static 元素写它无效。数值只在同一个层叠上下文内比较:
.map-grid { position: absolute; z-index: 1; } /* 模拟瓦片 */
.zoom-tools { position: absolute; z-index: 10; } /* 控件 */
.topbar { position: fixed; z-index: 100; } /* 顶栏 */
.feature-popup { position: absolute; z-index: 1000; } /* 弹窗 */
.toast { position: fixed; z-index: 2000; } /* 提示 */
两个最容易踩的规则:
transform、opacity < 1、filter、position: fixed等属性会让元素创建新的层叠上下文。子元素哪怕写z-index: 99999,也只能在父层级的内部比较,出不了这个“结界”。- 项目里不要随手堆 9999。约定普通内容 0
10、控件 10100、弹窗 1000、提示 2000,数字分层可预测,排查时也看得懂。
从右上角控件到吸顶表头
右上角缩放控件:
.map { position: relative; }
.zoom-tools { position: absolute; top: 12px; right: 12px; z-index: 10; }
左下角比例尺: 同一套“子绝父相”,换一个角:
.scalebar { position: absolute; left: 12px; bottom: 12px; }
固定顶栏: 见上文 fixed 写法,同时给 body 补等高 padding。
消息角标: 角标不占按钮的布局空间,挂在按钮右上角,常用负偏移:
.locate-btn { position: relative; }
.badge {
position: absolute;
top: -4px;
right: -4px;
min-width: 16px;
height: 16px;
border-radius: 8px;
background: #f5222d;
color: #fff;
font-size: 12px;
line-height: 16px;
text-align: center;
}
弹窗居中: top: 50%; left: 50% 只把弹窗的左上角放到容器中心,还要用 transform: translate(-50%, -50%) 把弹窗自己往回挪一半:
.feature-popup {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1000;
}
吸顶表头: 见上文 sticky 写法。表格放在带 overflow: auto 的容器里,th 就会随容器滚动并吸在容器顶部。
易错点
- 忘了“父相”:absolute 元素找不到定位祖先,会一路飞到页面甚至视口角落。控件贴错位置时,第一反应是查它向上几层的
position。 - 父元素用了 absolute 当参照:父元素也脱离文档流,容器可能塌成 0 高。单纯想当锚点就用
relative。 fixed被 transform 祖先“绑架”:祖先带transform、filter、perspective时,fixed的参照物会变成该祖先,顶栏可能跟着滚动。遇到这种怪事,查祖先有没有这类属性。- 给 static 元素写 z-index:普通元素写
z-index不生效,先确认它是定位元素,或者确实是 Flex/Grid 子项。 sticky缺top或缺背景:不写top就没有粘住位置;不写背景,滚动内容会从表头下面透出来。- 弹窗只写 50% 不写 translate:
top/left: 50%对齐的是左上角,弹窗会偏到右下;translate(-50%, -50%)才是“中心对中心”。
动手练习:07 定位与层叠
配套文件:02-web-basics/examples/07-定位与层叠.html。页面里放了一个大 div 扮演假地图,网格背景模拟瓦片,另有固定顶栏、长表格和滚动区。文件里有 7 组 TODO(第 3 组分 a/b 两步),按顺序完成。
参考答案:
/* TODO 1:父相(.map 规则里已写好,读一遍确认理解) */
position: relative;
/* TODO 2:贴进 .zoom-tools,缩放控件贴地图右上角 */
position: absolute;
top: 12px;
right: 12px;
z-index: 10;
/* TODO 3a:贴进 .topbar,固定顶栏 */
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
/* TODO 3b:贴进 body 规则,补出顶栏高度 */
padding-top: 56px;
/* TODO 4:贴进 .scalebar,比例尺贴地图左下角 */
position: absolute;
left: 12px;
bottom: 12px;
/* TODO 5:贴进 .badge,消息角标挂在按钮右上角 */
position: absolute;
top: -4px;
right: -4px;
min-width: 16px;
height: 16px;
border-radius: 8px;
background: #f5222d;
color: #fff;
font-size: 12px;
line-height: 16px;
text-align: center;
/* TODO 6:贴进 .feature-popup,要素弹窗居中 */
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1000;
/* TODO 7:贴进 th 规则,表头吸顶 */
position: sticky;
top: 0;
background: #fafafa;
对应 HTML:
<!-- TODO 4 -->
<div class="scalebar">0 ── 2 km</div>
<!-- TODO 5:放在“定位到我”按钮内部 -->
<span class="badge">3</span>
<!-- TODO 6:放在 .map 内部 -->
<div class="feature-popup">
<h3>武汉站</h3>
<p>坐标 30.59N, 114.30E</p>
</div>
做完 TODO 2 后,按注释里的实验把 z-index 改成 0:控件不会消失,因为普通内容默认也在 0 层;改成 -1 才会掉到假地图的网格背景下面。这个实验比背规则更能建立“层叠上下文”的直觉,观察完改回 10。
通关标准:
- 缩放控件贴地图右上角,比例尺贴左下角,而不是贴页面角落
- 滚动长内容时顶栏钉在视口顶部,表格表头吸在滚动容器顶部
- 角标挂在按钮右上角且不撑大按钮;弹窗居中于地图
- 能解释弹窗为什么用 1000、角标为什么用 10
自测题
absolute元素找不到任何定位祖先时,相对谁定位?- “子绝父相”是哪两个定位值的组合?为什么父元素不用 absolute?
fixed和sticky的本质区别?- z-index 写了 9999 却被别的元素压住,最可能的原因是什么?
- 固定顶栏后,页面最顶部的内容被顶栏遮住一截,怎么解决?
参考答案
- 相对初始包含块,约等于整个页面;不会只相对最近的普通父元素。
- 子元素
absolute+ 父元素relative。父元素用 absolute 会连自己也脱离文档流,布局可能塌陷;relative 既能留在原位,又能当定位锚点。 fixed默认相对视口,从头到尾钉死;sticky平时占位随流滚动,到达top等阈值后才在最近的滚动容器里粘住。- 祖先创建了新的层叠上下文(如
transform、opacity < 1、filter),把它的 z-index 限制在父层级内部。 - 给
body或主内容区加等高的padding-top(顶栏 56px 就补 56px)。
下一步
元素能排队、能浮起、能分层之后,还差最后一块:不同屏幕尺寸下同一套页面怎么适应。第 08 节讲响应式与媒体查询,把手机、平板、桌面一次收编。