跳到主要内容

定位 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 — 相对定位

相对于自身原始位置进行偏移,但元素仍然占据原来的空间

relative 定位
.box {
position: relative;
top: 20px; /* 向下偏移 20px */
left: 30px; /* 向右偏移 30px */
}
┌──────────────┐
│ 原始位置(空间仍保留)│
│ ┌──────────────┐
│ │ 视觉位置 │
│ │ (偏移后) │
│ └──────────────┘
└──────────────┘

常见用途:

  1. 微调元素位置(不影响布局)
  2. absolute 子元素提供定位基准
  3. 创建层叠上下文(配合 z-index

absolute — 绝对定位

脱离文档流,相对于最近的非 static 定位祖先(包含块)进行定位。如果找不到,则相对于初始包含块(通常是 <html> 的视口区域)。

absolute 定位
.parent {
position: relative; /* 作为定位基准 */
}
.child {
position: absolute;
top: 0;
right: 0; /* 定位到父元素右上角 */
}
absolute 的包含块
  • 如果祖先是块级元素:包含块是该祖先的 padding box
  • 如果祖先是行内元素:包含块是祖先的 content box 的边界
  • 没有合适祖先时:包含块是 初始包含块(根元素的视口区域)

absolute 元素的宽度默认由内容决定(不再是块级元素的 100% 宽度)。

fixed — 固定定位

相对于视口定位,不随页面滚动。

fixed 定位
.header {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
}
fixed 失效的情况

当祖先元素设置了以下属性时,fixed 不再相对于视口定位,而是相对于该祖先:

  • transform(不为 none
  • perspective(不为 none
  • filter(不为 none
  • will-changetransform/perspective/filter
  • contain: paint
.parent {
transform: translateZ(0); /* 即使没有实际变换 */
}
.child {
position: fixed; /* 不再相对视口!而是相对 .parent */
}

这是实际开发中常见的 bug 来源。

sticky — 粘性定位

元素在滚动到指定阈值之前表现为 relative,到达阈值后表现为 fixed

sticky 定位
.section-title {
position: sticky;
top: 0; /* 滚动时粘在距视口顶部 0 的位置 */
}
sticky 生效条件
  1. 必须设置 top/bottom/left/right 中的至少一个
  2. 父元素不能设置 overflow: hidden/auto/scroll(会创建新的滚动容器,导致 sticky 在该容器内粘附而非视口)
  3. 粘附范围限于父元素的边界,父元素滚出视口时 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 < 1opacity: 0.99
transform ≠ nonetransform: translateZ(0)
filter ≠ nonefilter: blur(0)
isolation: isolate专门用于创建层叠上下文
Flex/Grid 子项 + z-index ≠ autoFlex/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 只在同一层叠上下文内比较

如果两个元素在不同的层叠上下文中,它们的 z-index 不直接比较。而是由它们所在的层叠上下文在父级中的顺序决定。

/* 父 A 的 z-index: 1,父 B 的 z-index: 2 */
/* 即使 A 内部的子元素 z-index: 9999,也比 B 内部的 z-index: 1 低 */

inset 简写

insettoprightbottomleft 的简写:

.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(滚动吸附)。

其中 absolutefixed 脱离文档流relativesticky 不脱离

Q2: absolute 的定位基准是什么?

答案

向上查找最近的 position 不为 static 的祖先元素的 padding box。如果找不到,相对于初始包含块(通常是视口区域)定位。

常见做法:给父元素设 position: relative 作为定位基准。

Q3: relativeabsolute 的区别?

答案

特性relativeabsolute
是否脱离文档流(保留原位置)
偏移基准自身原始位置最近非 static 祖先
宽度默认值父元素宽度内容决定
影响周围元素不影响不影响(已脱离)
常见用途微调位置、为子元素提供定位基准叠加效果、弹窗、Badge

Q4: fixed 在什么情况下会失效?

答案

当祖先元素设置了 transformperspectivefilterwill-change 等创建新包含块的属性时,fixed 定位不再相对视口,而是相对于该祖先。

常见场景:

  • Modal 弹窗内的 fixed 元素表现异常
  • 使用 transform: translateZ(0) 做 GPU 加速时影响子元素的 fixed

解决方案:将 fixed 元素移到受影响的祖先元素外部。

Q5: 什么是层叠上下文?z-index 不生效的原因?

答案

层叠上下文是 z 轴方向的独立分层区域。z-index 不生效的常见原因:

  1. 没有设置 positionz-index 只在 position 不为 static 时生效(Flex/Grid 子项除外)
  2. 父元素层叠上下文限制:父元素的 z-index 较低时,子元素 z-index 再大也无法超越父级的兄弟元素
  3. 不在同一层叠上下文:z-index 只在同一层叠上下文内比较
/* z-index 不生效 */
.box {
position: static; /* 需要改为 relative/absolute */
z-index: 99; /* 无效! */
}

Q6: sticky 的工作原理是什么?为什么有时不生效?

答案

sticky 在正常流位置和"粘附"位置之间切换:

  • 滚动未到阈值(如 top: 0):表现为 relative
  • 滚动到阈值后:表现为 fixed
  • 滚出父元素边界:跟随父元素离开

不生效的原因:

  1. 未设置 top/bottom/left/right
  2. 父元素或祖先元素设置了 overflow: hidden/auto/scroll
  3. 父元素高度不足(没有多余的滚动空间)
  4. 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 还是被遮挡怎么办?

答案

原因几乎都是层叠上下文问题。排查步骤:

  1. 用 DevTools 检查遮挡元素的层叠上下文
  2. 查看目标元素的所有祖先是否创建了层叠上下文(transformopacity 等)
  3. 比较两个元素所在层叠上下文的 z-index

解决方案:

  • 调整 DOM 结构,让目标元素与遮挡元素在同一层叠上下文
  • 使用 isolation: isolate 管理层叠上下文
  • 将弹窗类元素放到 <body> 直接子元素(如 React 的 Portal)

Q9: position: absolute 的元素宽度是如何计算的?

答案

  • 未设置 width/left/right:宽度由内容决定(收缩到内容大小)
  • 设置了 leftright(未设置 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: 0isolation: isolate 语义更清晰。

相关链接