定位 position
问题
CSS 的 position 有哪些值?absolute 的定位基准是什么?什么是层叠上下文?z-index 怎么工作?
答案
position 取值
| 值 | 说明 | 是否脱离文档流 | 定位基准 |
|---|---|---|---|
static | 默认值,按正常流排列 | 否 | 无(不可使用 top/left 等) |
relative | 相对自身原始位置偏移 | 否(仍占据原位置) | 自身原始位置 |
absolute | 相对最近的非 static 祖先定位 | 是 | 包含块 |
fixed | 相对视口定位 | 是 | 视口(viewport) |
sticky | 在滚动到阈值前为 relative,之后为 fixed | 否 | 最近的滚动祖先 |
static — 默认定位
.box {
position: static; /* 默认值 */
top: 10px; /* 无效!static 不接受偏移属性 */
}
relative — 相对定位
相对于自身原始位置进行偏移,但元素仍然占据原来的空间。
.box {
position: relative;
top: 20px; /* 向下偏移 20px */
left: 30px; /* 向右偏移 30px */
}
┌──────────────┐
│ 原始位置(空间仍保留)│
│ ┌──────────────┐
│ │ 视觉位置 │
│ │ (偏移后) │
│ └──────────────┘
└──────────────┘
常见用途:
- 微调元素位置(不影响布局)
- 为
absolute子元素提供定位基准 - 创建层叠上下文(配合
z-index)
absolute — 绝对定位
脱离文档流,相对于最近的非 static 定位祖先(包含块)进行定位。如果找不到,则相对于初始包含块(通常是 <html> 的视口区域)。
.parent {
position: relative; /* 作为定位基准 */
}
.child {
position: absolute;
top: 0;
right: 0; /* 定位到父元素右上角 */
}
- 如果祖先是块级元素:包含块是该祖先的 padding box
- 如果祖先是行内元素:包含块是祖先的 content box 的边界
- 没有合适祖先时:包含块是 初始包含块(根元素的视口区域)
absolute 元素的宽度默认由内容决定(不再是块级元素的 100% 宽度)。
fixed — 固定定位
相对于视口定位,不随页面滚动。
.header {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
}
当祖先元素设置了以下属性时,fixed 不再相对于视口定位,而是相对于该祖先:
transform(不为none)perspective(不为none)filter(不为none)will-change为transform/perspective/filtercontain: paint
.parent {
transform: translateZ(0); /* 即使没有实际变换 */
}
.child {
position: fixed; /* 不再相对视口!而是相对 .parent */
}
这是实际开发中常见的 bug 来源。
sticky — 粘性定位
元素在滚动到指定阈值之前表现为 relative,到达阈值后表现为 fixed。
.section-title {
position: sticky;
top: 0; /* 滚动时粘在距视口顶部 0 的位置 */
}
- 必须设置
top/bottom/left/right中的至少一个 - 父元素不能设置
overflow: hidden/auto/scroll(会创建新的滚动容器,导致 sticky 在该容器内粘附而非视口) - 粘附范围限于父元素的边界,父元素滚出视口时 sticky 子元素也跟着走
table thead th {
position: sticky;
top: 0;
background: white;
z-index: 1;
}
层叠上下文(Stacking Context)
层叠上下文是 HTML 元素在 z 轴方向上的分层机制。每个层叠上下文内部的 z-index 是独立的。
创建层叠上下文的条件
| 条件 | 说明 |
|---|---|
根元素 <html> | 自动创建 |
position: relative/absolute + z-index ≠ auto | 最经典的方式 |
position: fixed / sticky | 始终创建 |
opacity < 1 | 如 opacity: 0.99 |
transform ≠ none | 如 transform: translateZ(0) |
filter ≠ none | 如 filter: blur(0) |
isolation: isolate | 专门用于创建层叠上下文 |
Flex/Grid 子项 + z-index ≠ auto | Flex/Grid 的特殊规则 |
will-change 指定特定属性 | 如 will-change: transform |
contain: layout/paint | |
mix-blend-mode ≠ normal |
层叠顺序(从下到上)
7. z-index > 0 ← 正 z-index(值越大越上面)
6. z-index: 0 / auto ← z-index 为 0 或 auto
5. inline/inline-block ← 行内元素
4. float ← 浮动元素
3. block ← 块级元素(正常流)
2. z-index < 0 ← 负 z-index
1. background/border ← 层叠上下文的背景和边框
如果两个元素在不同的层叠上下文中,它们的 z-index 不直接比较。而是由它们所在的层叠上下文在父级中的顺序决定。
/* 父 A 的 z-index: 1,父 B 的 z-index: 2 */
/* 即使 A 内部的子元素 z-index: 9999,也比 B 内部的 z-index: 1 低 */
inset 简写
inset 是 top、right、bottom、left 的简写:
.child {
position: absolute;
inset: 0; /* top:0 right:0 bottom:0 left:0 */
inset: 10px 20px; /* 上下10px 左右20px */
inset: 10px 20px 30px 40px; /* 上 右 下 左 */
}
常见面试问题
Q1: position 有哪些值?分别什么含义?
答案:
5 个值:static(默认,正常流)、relative(相对自身偏移)、absolute(相对非 static 祖先)、fixed(相对视口)、sticky(滚动吸附)。
其中 absolute 和 fixed 脱离文档流,relative 和 sticky 不脱离。
Q2: absolute 的定位基准是什么?
答案:
向上查找最近的 position 不为 static 的祖先元素的 padding box。如果找不到,相对于初始包含块(通常是视口区域)定位。
常见做法:给父元素设 position: relative 作为定位基准。
Q3: relative 和 absolute 的区别?
答案:
| 特性 | relative | absolute |
|---|---|---|
| 是否脱离文档流 | 否(保留原位置) | 是 |
| 偏移基准 | 自身原始位置 | 最近非 static 祖先 |
| 宽度默认值 | 父元素宽度 | 由内容决定 |
| 影响周围元素 | 不影响 | 不影响(已脱离) |
| 常见用途 | 微调位置、为子元素提供定位基准 | 叠加效果、弹窗、Badge |
Q4: fixed 在什么情况下会失效?
答案:
当祖先元素设置了 transform、perspective、filter、will-change 等创建新包含块的属性时,fixed 定位不再相对视口,而是相对于该祖先。
常见场景:
- Modal 弹窗内的
fixed元素表现异常 - 使用
transform: translateZ(0)做 GPU 加速时影响子元素的fixed
解决方案:将 fixed 元素移到受影响的祖先元素外部。
Q5: 什么是层叠上下文?z-index 不生效的原因?
答案:
层叠上下文是 z 轴方向的独立分层区域。z-index 不生效的常见原因:
- 没有设置
position:z-index只在position不为static时生效(Flex/Grid 子项除外) - 父元素层叠上下文限制:父元素的 z-index 较低时,子元素 z-index 再大也无法超越父级的兄弟元素
- 不在同一层叠上下文:z-index 只在同一层叠上下文内比较
/* z-index 不生效 */
.box {
position: static; /* 需要改为 relative/absolute */
z-index: 99; /* 无效! */
}
Q6: sticky 的工作原理是什么?为什么有时不生效?
答案:
sticky 在正常流位置和"粘附"位置之间切换:
- 滚动未到阈值(如
top: 0):表现为relative - 滚动到阈值后:表现为
fixed - 滚出父元素边界:跟随父元素离开
不生效的原因:
- 未设置
top/bottom/left/right - 父元素或祖先元素设置了
overflow: hidden/auto/scroll - 父元素高度不足(没有多余的滚动空间)
- Flex 容器中的
align-items: stretch导致高度相同
Q7: 如何用 CSS 实现一个固定在右下角的返回顶部按钮?
答案:
.back-to-top {
position: fixed;
right: 24px;
bottom: 24px;
z-index: 50;
width: 48px;
height: 48px;
border-radius: 50%;
cursor: pointer;
}
Q8: z-index: 9999 还是被遮挡怎么办?
答案:
原因几乎都是层叠上下文问题。排查步骤:
- 用 DevTools 检查遮挡元素的层叠上下文
- 查看目标元素的所有祖先是否创建了层叠上下文(
transform、opacity等) - 比较两个元素所在层叠上下文的 z-index
解决方案:
- 调整 DOM 结构,让目标元素与遮挡元素在同一层叠上下文
- 使用
isolation: isolate管理层叠上下文 - 将弹窗类元素放到
<body>直接子元素(如 React 的 Portal)
Q9: position: absolute 的元素宽度是如何计算的?
答案:
- 未设置
width/left/right:宽度由内容决定(收缩到内容大小) - 设置了
left和right(未设置width):宽度 = 包含块宽度 - left - right - 设置了
width:使用指定的宽度
/* 宽度由内容决定 */
.abs { position: absolute; top: 0; }
/* 宽度 = 包含块宽度 - 20px - 20px */
.abs { position: absolute; left: 20px; right: 20px; }
/* 宽度 = 300px */
.abs { position: absolute; width: 300px; }
Q10: isolation: isolate 有什么用?
答案:
isolation: isolate 的唯一作用是创建新的层叠上下文,且没有任何副作用。
常见用途:控制组件内部的 z-index 不影响外部:
.modal-overlay {
isolation: isolate; /* 创建独立的层叠上下文 */
}
.modal-overlay .content {
z-index: 10; /* 只在 .modal-overlay 内部生效 */
}
相比 position: relative; z-index: 0,isolation: isolate 语义更清晰。