←返回笔记列表
01 · NOTES / WebGIS 开发

第 07 节 · 定位与层叠

2026 年 9 月 7 日

第 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; }        /* 提示 */

两个最容易踩的规则:

  1. transform、opacity < 1、filter、position: fixed 等属性会让元素创建新的层叠上下文。子元素哪怕写 z-index: 99999,也只能在父层级的内部比较,出不了这个“结界”。
  2. 项目里不要随手堆 9999。约定普通内容 010、控件 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

自测题

  1. absolute 元素找不到任何定位祖先时,相对谁定位?
  2. “子绝父相”是哪两个定位值的组合?为什么父元素不用 absolute?
  3. fixed 和 sticky 的本质区别?
  4. z-index 写了 9999 却被别的元素压住,最可能的原因是什么?
  5. 固定顶栏后,页面最顶部的内容被顶栏遮住一截,怎么解决?

参考答案

  1. 相对初始包含块,约等于整个页面;不会只相对最近的普通父元素。
  2. 子元素 absolute + 父元素 relative。父元素用 absolute 会连自己也脱离文档流,布局可能塌陷;relative 既能留在原位,又能当定位锚点。
  3. fixed 默认相对视口,从头到尾钉死;sticky 平时占位随流滚动,到达 top 等阈值后才在最近的滚动容器里粘住。
  4. 祖先创建了新的层叠上下文(如 transform、opacity < 1、filter),把它的 z-index 限制在父层级内部。
  5. 给 body 或主内容区加等高的 padding-top(顶栏 56px 就补 56px)。

下一步

元素能排队、能浮起、能分层之后,还差最后一块:不同屏幕尺寸下同一套页面怎么适应。第 08 节讲响应式与媒体查询,把手机、平板、桌面一次收编。

TAGSweb