Skip to content

CSS 定位机制:精确控制元素在页面中的位置

想象你正在布置一个舞台。大部分演员按照剧本规定的顺序依次上场,这是正常的流程。但有时候,你需要让某个演员站在特定的位置——可能是相对于他原本该站的地方稍微偏一点,可能是相对于舞台的某个固定位置,甚至可能需要"飘"在舞台上方。CSS 的定位机制就是这样一套系统,让你可以精确控制元素在页面中的位置。

理解文档流和定位

在学习定位之前,我们需要先理解一个基础概念:文档流(Document Flow)。

文档流就像是一条河流,HTML 元素就像是河里的船。默认情况下,所有的船都按照顺序排列,一艘接一艘地顺流而下。Block 元素独占一整行(像大船),Inline 元素挤在一起(像小船)。这种自然的、有序的排列就是"正常文档流"。

当我们使用 position 属性时,就像是给某些船安装了特殊的装置,让它们可以脱离这条河流,移动到其他地方。有的船只是稍微偏离原位,有的船完全脱离了河流。

Static:默认的文档流定位

position: static 是所有元素的默认定位方式。这个值表示元素处于正常文档流中,遵循标准的布局规则。

css
.normal-element {
  position: static; /* 默认值,通常不需要显式设置 */
}

Static 定位的元素有几个特点:

  • 按照正常文档流排列
  • top、right、bottom、left 这些方位属性对它完全无效
  • z-index 也无效

在实际开发中,我们很少主动设置 position: static,因为这是默认值。但有时候我们需要覆盖之前设置的定位,这时候用 position: static 就可以让元素恢复到正常文档流。

css
.element {
  position: absolute; /* 之前被设置为绝对定位 */
}

/* 在某个媒体查询中恢复正常流 */
@media (max-width: 768px) {
  .element {
    position: static; /* 移动端恢复正常流 */
  }
}

Relative:相对定位

相对定位就像是给演员一个小提示:"你还是按照剧本的位置站,但稍微往左移一点点。"元素相对于它原本应该在的位置进行偏移,但重要的是,它原本的空间还保留着。

Relative 的核心特点

html
<div class="container">
  <div class="box">盒子 1</div>
  <div class="box relative-box">盒子 2(相对定位)</div>
  <div class="box">盒子 3</div>
</div>
css
.box {
  width: 200px;
  height: 100px;
  background-color: #e3f2fd;
  margin: 10px;
}

.relative-box {
  position: relative;
  top: 20px; /* 相对于原位置向下移动20px */
  left: 30px; /* 相对于原位置向右移动30px */
  background-color: #ffeb3b;
}

在这个例子中,盒子 2 会向下移动 20px,向右移动 30px,但它原本的位置(盒子 1 和盒子 3 之间)仍然保留着。盒子 3 不会上移来填补空间。

这就像舞台上的演员虽然稍微移动了位置,但他原本的"站位"还在那里,其他演员不会去占这个位置。

方位属性的含义

对于相对定位,方位属性的含义是相对于元素的原始位置:

css
.element {
  position: relative;
  top: 10px; /* 相对原位置向下移动10px */
  right: 20px; /* 相对原位置向左移动20px */
  bottom: 15px; /* 相对原位置向上移动15px */
  left: 25px; /* 相对原位置向右移动25px */
}

注意这里有个容易混淆的点:top: 10px 不是"距离顶部 10px",而是"从原位置向下移动 10px"。同样,right: 20px 是"从原位置向左移动 20px"。

如果同时设置相反的方位(比如同时设置 top 和 bottom),通常 top 会生效,bottom 会被忽略。水平方向上,left 的优先级高于 right。

Relative 的实际应用

1. 微调元素位置

有时候布局已经基本完成,但某个元素需要微调几个像素,使用相对定位是最简单的方法:

css
.icon {
  position: relative;
  top: 2px; /* 让图标稍微向下一点,以便和文字对齐 */
}

2. 作为绝对定位的参照物

这是相对定位最常见的用途。当我们想让一个绝对定位的元素相对于某个特定的父元素定位时,就需要给父元素设置 position: relative

css
.card {
  position: relative; /* 成为子元素的定位参照 */
  padding: 20px;
}

.badge {
  position: absolute; /* 相对于 .card 定位 */
  top: 10px;
  right: 10px;
}

在这个场景中,我们给父元素设置 position: relative,但不设置任何方位属性(top、right 等都不设置),这样父元素本身不会移动,但可以作为子元素的定位参照。

3. 设置层叠顺序

相对定位的元素可以使用 z-index,这在需要调整元素的层叠顺序时很有用:

css
.overlay-trigger {
  position: relative;
  z-index: 10; /* 确保这个元素在其他元素之上 */
}

Absolute:绝对定位

绝对定位就像是给演员装上了飞行装置,让他完全脱离舞台的正常流程,可以飘到舞台上的任意位置。

Absolute 的核心特点

当元素设置为 position: absolute 时,会发生以下几件事:

  1. 完全脱离文档流:元素不再占据原本的空间,后面的元素会上移填补
  2. 相对于定位祖先元素定位:元素会相对于最近的已定位(position 不是 static)的祖先元素定位
  3. 如果没有已定位的祖先:元素会相对于初始包含块(通常是 <html> 元素)定位
html
<div class="container">
  <div class="box">盒子 1</div>
  <div class="box absolute-box">盒子 2(绝对定位)</div>
  <div class="box">盒子 3</div>
</div>
css
.box {
  width: 200px;
  height: 100px;
  background-color: #e3f2fd;
  margin: 10px;
}

.absolute-box {
  position: absolute;
  top: 50px;
  left: 100px;
  background-color: #f44336;
  color: white;
}

在这个例子中,盒子 2 会完全脱离文档流,盒子 3 会上移到盒子 2 原本的位置。盒子 2 会相对于最近的已定位祖先元素(如果 .container 没有设置定位,就会相对于 <body><html>)定位到距离顶部 50px、距离左侧 100px 的位置。

定位上下文的查找规则

"定位上下文"是理解绝对定位的关键概念。绝对定位的元素会向上查找最近的已定位祖先元素作为参照。

查找规则是:

  1. 从父元素开始,向上查找
  2. 如果父元素的 position 是 relative、absolute、fixed 或 sticky,停止查找,就用这个元素作为参照
  3. 如果父元素的 position 是 static(或没设置),继续向上查找
  4. 如果一直找到 <html> 还没有找到,就以初始包含块为参照

让我们看一个实际的例子:

html
<div class="grandparent">
  <div class="parent">
    <div class="child">我相对于谁定位?</div>
  </div>
</div>

场景 1:父元素有定位

css
.parent {
  position: relative; /* 有定位 */
  background-color: #e3f2fd;
  padding: 40px;
}

.child {
  position: absolute;
  top: 10px;
  left: 10px;
  background-color: #ff9800;
}

.child 会相对于 .parent 定位,距离 .parent 的顶部和左侧各 10px。

场景 2:父元素无定位,祖父元素有定位

css
.grandparent {
  position: relative; /* 祖父元素有定位 */
  background-color: #f5f5f5;
  padding: 50px;
}

.parent {
  /* 没有定位 */
  background-color: #e3f2fd;
  padding: 40px;
}

.child {
  position: absolute;
  top: 10px;
  left: 10px;
}

由于 .parent 没有定位,.child 会向上查找,找到 .grandparent,相对于它定位。

场景 3:都没有定位

css
/* 所有祖先元素都没有设置定位 */
.child {
  position: absolute;
  top: 10px;
  left: 10px;
}

.child 会相对于 <html> 元素定位,距离浏览器窗口的顶部和左侧各 10px。

绝对定位的尺寸行为

绝对定位的元素有一些特殊的尺寸行为:

1. 默认宽度由内容决定

不同于 block 元素默认填满父容器,绝对定位的元素默认宽度由内容决定:

css
.absolute-element {
  position: absolute;
  /* 宽度由内容决定,不会自动填满 */
  background-color: #4caf50;
  padding: 10px;
}

2. 可以通过四个方位属性来控制尺寸

这是一个很有用的技巧:

css
.fullsize-overlay {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  /* 不需要设置 width 和 height,元素会自动填满定位上下文 */
  background-color: rgba(0, 0, 0, 0.5);
}

通过同时设置四个方位属性为 0,元素会自动填满整个定位上下文。

绝对定位的实际应用

1. 模态框/弹窗

html
<div class="modal">
  <div class="modal-overlay"></div>
  <div class="modal-content">
    <h2>弹窗标题</h2>
    <p>这是弹窗内容...</p>
    <button class="close-btn">×</button>
  </div>
</div>
css
.modal {
  position: fixed; /* 父容器固定定位,方便子元素定位 */
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 1000;
}

.modal-overlay {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background-color: rgba(0, 0, 0, 0.5);
}

.modal-content {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%); /* 居中技巧 */
  background-color: white;
  padding: 30px;
  border-radius: 8px;
  max-width: 500px;
}

.close-btn {
  position: absolute;
  top: 15px;
  right: 15px;
  background: none;
  border: none;
  font-size: 24px;
  cursor: pointer;
}

2. 角标/徽章

html
<div class="notification-icon">
  <img src="bell.svg" alt="Notifications" />
  <span class="badge">5</span>
</div>
css
.notification-icon {
  position: relative; /* 成为定位参照 */
  display: inline-block;
}

.badge {
  position: absolute;
  top: -8px;
  right: -8px;
  background-color: #f44336;
  color: white;
  border-radius: 50%;
  width: 20px;
  height: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
}

3. 下拉菜单

html
<div class="dropdown">
  <button class="dropdown-trigger">下拉菜单</button>
  <div class="dropdown-menu">
    <a href="#">选项 1</a>
    <a href="#">选项 2</a>
    <a href="#">选项 3</a>
  </div>
</div>
css
.dropdown {
  position: relative;
  display: inline-block;
}

.dropdown-menu {
  position: absolute;
  top: 100%; /* 紧贴触发按钮下方 */
  left: 0;
  min-width: 200px;
  background-color: white;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
  border-radius: 4px;
  display: none; /* 默认隐藏 */
  z-index: 100;
}

.dropdown:hover .dropdown-menu {
  display: block; /* 鼠标悬停时显示 */
}

.dropdown-menu a {
  display: block;
  padding: 10px 15px;
  color: #333;
  text-decoration: none;
}

.dropdown-menu a:hover {
  background-color: #f5f5f5;
}

4. 工具提示(Tooltip)

html
<span class="tooltip-wrapper">
  鼠标悬停在这里
  <span class="tooltip">这是提示文字</span>
</span>
css
.tooltip-wrapper {
  position: relative;
  cursor: help;
  border-bottom: 1px dotted #999;
}

.tooltip {
  position: absolute;
  bottom: 100%; /* 在触发元素上方 */
  left: 50%;
  transform: translateX(-50%);
  background-color: #333;
  color: white;
  padding: 8px 12px;
  border-radius: 4px;
  font-size: 14px;
  white-space: nowrap;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.3s, visibility 0.3s;
}

.tooltip::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  border: 6px solid transparent;
  border-top-color: #333; /* 小三角 */
}

.tooltip-wrapper:hover .tooltip {
  opacity: 1;
  visibility: visible;
}

Fixed:固定定位

固定定位就像是把元素钉在浏览器窗口的某个位置上,无论页面如何滚动,元素都保持在视口的固定位置。

Fixed 的核心特点

css
.fixed-header {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  background-color: #1976d2;
  color: white;
  padding: 15px;
  z-index: 1000;
}

固定定位的元素:

  • 完全脱离文档流
  • 相对于浏览器视口(viewport)定位
  • 不受页面滚动影响
  • 始终停留在视口的固定位置

Fixed 与 Absolute 的区别

虽然两者都脱离文档流,但定位参照不同:

  • Absolute:相对于最近的已定位祖先元素
  • Fixed:始终相对于浏览器视口
html
<div class="scrollable-container" style="height: 2000px;">
  <div class="absolute-element">绝对定位</div>
  <div class="fixed-element">固定定位</div>
</div>
css
.scrollable-container {
  position: relative;
}

.absolute-element {
  position: absolute;
  top: 100px;
  left: 50px;
  /* 会随着页面滚动而滚动 */
}

.fixed-element {
  position: fixed;
  top: 100px;
  right: 50px;
  /* 不会随页面滚动,始终在视口的固定位置 */
}

Fixed 的实际应用

1. 固定导航栏

css
.navbar {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  background-color: white;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  z-index: 1000;
  /* 移动端可能需要设置 padding-top 给 body,避免内容被遮挡 */
}

body {
  padding-top: 60px; /* 导航栏的高度 */
}

2. 返回顶部按钮

html
<button class="back-to-top" id="backToTop">↑</button>
css
.back-to-top {
  position: fixed;
  bottom: 30px;
  right: 30px;
  width: 50px;
  height: 50px;
  background-color: #2196f3;
  color: white;
  border: none;
  border-radius: 50%;
  font-size: 24px;
  cursor: pointer;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.3s, visibility 0.3s;
  z-index: 999;
}

.back-to-top.visible {
  opacity: 1;
  visibility: visible;
}
javascript
const backToTop = document.getElementById("backToTop");

window.addEventListener("scroll", () => {
  if (window.scrollY > 300) {
    backToTop.classList.add("visible");
  } else {
    backToTop.classList.remove("visible");
  }
});

backToTop.addEventListener("click", () => {
  window.scrollTo({ top: 0, behavior: "smooth" });
});

3. 侧边工具栏

css
.sidebar-tools {
  position: fixed;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  background-color: white;
  padding: 10px;
  box-shadow: -2px 0 10px rgba(0, 0, 0, 0.1);
  border-radius: 8px 0 0 8px;
}

.tool-btn {
  display: block;
  width: 50px;
  height: 50px;
  margin: 10px 0;
  border: none;
  background-color: #f5f5f5;
  cursor: pointer;
  border-radius: 4px;
}

4. Fixed 布局(管理后台)

html
<div class="admin-layout">
  <header class="admin-header">顶部导航</header>
  <aside class="admin-sidebar">侧边栏</aside>
  <main class="admin-content">主内容区</main>
</div>
css
.admin-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 60px;
  background-color: #1976d2;
  z-index: 1000;
}

.admin-sidebar {
  position: fixed;
  top: 60px;
  left: 0;
  bottom: 0;
  width: 250px;
  background-color: #263238;
  overflow-y: auto;
  z-index: 999;
}

.admin-content {
  margin-top: 60px;
  margin-left: 250px;
  padding: 30px;
  min-height: calc(100vh - 60px);
}

Sticky:粘性定位

粘性定位是相对定位和固定定位的混合体。元素在达到某个阈值之前表现为相对定位,达到阈值后表现为固定定位。

Sticky 的工作原理

想象一个便利贴:在你翻书的时候,便利贴会随着页面移动(相对定位),但当它到达书的顶部时,就会"粘"在那里不动(固定定位),直到你翻过这一页。

css
.sticky-header {
  position: sticky;
  top: 0; /* 粘性阈值:距离顶部0px时开始固定 */
  background-color: white;
  padding: 15px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  z-index: 10;
}

理解 Sticky 的定位参照:滚动祖先

这是理解 sticky 最关键的概念:sticky 元素会相对于它最近的"滚动祖先"(scrolling ancestor)进行定位

什么是滚动祖先?

滚动祖先是指从 sticky 元素开始,向上查找的第一个满足以下条件的祖先元素:

  • 设置了 overflow: autooverflow: scrolloverflow: hidden(任何非 visible 的值)
  • 并且这个元素确实产生了滚动(有滚动条或内容溢出)

如果找不到这样的祖先,sticky 元素就相对于视口(viewport)定位。

Sticky 的触发条件

条件 1:必须设置阈值

css
.sticky-element {
  position: sticky;
  top: 20px; /* 必须设置 top/right/bottom/left */
}

条件 2:必须有滚动空间

场景 A:直接在滚动容器中(✅ 推荐)

html
<div class="scrollable-container">
  <div class="sticky-item">我会粘住</div>
  <div class="content">很长的内容...</div>
</div>
css
.scrollable-container {
  height: 400px;
  overflow-y: auto; /* 这是滚动容器 */
}

.sticky-item {
  position: sticky;
  top: 0; /* 相对于 .scrollable-container 定位 */
}

✅ 正常工作:.scrollable-container 是 sticky 元素的"滚动祖先"。

**场景 B:中间有 overflow 的 wrapper(❌ 常见陷阱) **

html
<div class="scrollable-container">
  <div class="wrapper">
    <div class="sticky-item">我不会正常粘住!</div>
  </div>
  <div class="content">很长的内容...</div>
</div>
css
.scrollable-container {
  height: 400px;
  overflow-y: auto; /* 外层滚动容器 */
}

.wrapper {
  overflow: hidden; /* ❌ 问题所在! */
}

.sticky-item {
  position: sticky;
  top: 0;
}

为什么会"失效"?

  1. 浏览器从 .sticky-item 向上查找"滚动祖先"
  2. 首先找到 .wrapper,它有 overflow: hidden
  3. 浏览器认为 .wrapper 是滚动祖先
  4. .wrapper 本身不滚动(高度由内容决定)
  5. 没有滚动,sticky 不会被触发

实际上 sticky 并没有失效,它只是相对于错误的容器定位了。

场景 C:中间有正常的 wrapper(✅ 正确)

html
<div class="scrollable-container">
  <div class="wrapper">
    <div class="sticky-item">我会粘住</div>
  </div>
  <div class="content">很长的内容...</div>
</div>
css
.scrollable-container {
  height: 400px;
  overflow-y: auto;
}

.wrapper {
  /* 不设置 overflow,或设置为 visible */
  padding: 20px;
}

.sticky-item {
  position: sticky;
  top: 0;
}

✅ 正常工作:浏览器跳过 .wrapper,找到 .scrollable-container

条件 3:Sticky 元素只在包含块范围内粘住

html
<section class="section">
  <h2 class="sticky-title">章节 1 标题</h2>
  <p>章节 1 内容...</p>
</section>
<section class="section">
  <h2 class="sticky-title">章节 2 标题</h2>
  <p>章节 2 内容...</p>
</section>
css
.sticky-title {
  position: sticky;
  top: 0;
  background-color: #f5f5f5;
  /* 只在各自的 .section 范围内粘住 */
}

当滚动到章节 2 时,章节 1 的标题会被推出视口,章节 2 的标题开始粘住。

Sticky 的调试技巧

当 sticky 不工作时,按照以下步骤排查:

步骤 1:检查阈值

css
/* ❌ 没有阈值 */
.sticky {
  position: sticky;
}

/* ✅ 有阈值 */
.sticky {
  position: sticky;
  top: 0;
}

步骤 2:检查滚动祖先(最重要!)

使用浏览器开发者工具:

  1. 选中 sticky 元素
  2. 向上检查每个祖先元素的 overflow
  3. 找到第一个 overflow 不为 visible 的元素
  4. 确认这个元素是否真的可以滚动
  5. 如果这个元素不是你期望的滚动容器,移除它的 overflow

步骤 3:使用最简测试

html
<!DOCTYPE html>
<html>
  <head>
    <style>
      body {
        height: 2000px;
      }
      .sticky {
        position: sticky;
        top: 0;
        background: yellow;
        padding: 10px;
      }
    </style>
  </head>
  <body>
    <div class="sticky">我应该会粘住</div>
    <p>向下滚动...</p>
  </body>
</html>

如果这个最简单的例子能工作,逐步添加你的实际结构,找出问题。

Sticky 的实际应用

1. 表格标题固定

html
<table class="data-table">
  <thead>
    <tr class="sticky-header">
      <th>姓名</th>
      <th>年龄</th>
      <th>城市</th>
    </tr>
  </thead>
  <tbody>
    <!-- 100 行数据 -->
  </tbody>
</table>
css
.data-table {
  width: 100%;
  border-collapse: collapse;
}

.sticky-header {
  position: sticky;
  top: 0;
  background-color: #1976d2;
  color: white;
  z-index: 10;
}

.sticky-header th {
  padding: 15px;
  text-align: left;
}

2. 章节标题

css
.chapter-title {
  position: sticky;
  top: 0;
  background-color: #f5f5f5;
  padding: 15px;
  margin: 0 -15px; /* 扩展到容器边缘 */
  border-bottom: 2px solid #e0e0e0;
  z-index: 5;
}

3. 侧边栏导航

html
<div class="content-wrapper">
  <aside class="sidebar">
    <nav class="sticky-nav">
      <a href="#section1">章节 1</a>
      <a href="#section2">章节 2</a>
      <a href="#section3">章节 3</a>
    </nav>
  </aside>
  <main class="main-content">
    <!-- 内容 -->
  </main>
</div>
css
.content-wrapper {
  display: flex;
  gap: 30px;
}

.sidebar {
  flex: 0 0 250px;
}

.sticky-nav {
  position: sticky;
  top: 20px; /* 距离顶部20px时开始固定 */
}

.main-content {
  flex: 1;
}

Sticky 不生效的常见原因

问题 1:没有设置阈值

css
/* ❌ 不会生效 */
.element {
  position: sticky;
  /* 缺少 top/right/bottom/left */
}

/* ✅ 正确 */
.element {
  position: sticky;
  top: 0;
}

问题 2:父容器高度不够

css
/* ❌ sticky元素无法滚动 */
.parent {
  height: 200px; /* 高度太小 */
}

.sticky-child {
  height: 150px;
  position: sticky;
  top: 0;
}

如果 sticky 元素的高度接近父容器高度,就没有滚动空间,sticky 不会触发。

问题 3:父容器设置了 overflow

css
/* ❌ sticky 失效 */
.parent {
  overflow: hidden;
}

/* ✅ 移除overflow或设置在祖先元素上 */
.grandparent {
  overflow: hidden;
}

.parent {
  /* 不设置overflow */
}

Z-Index 与层叠上下文

当多个定位元素重叠时,谁在上面谁在下面?这就是 z-index 要解决的问题。

理解 Z 轴

我们通常把网页看作是二维的(X 轴和 Y 轴),但实际上还有第三个维度:Z 轴,也就是"深度"。Z 轴垂直于屏幕,指向你的方向。

z-index 就是控制元素在 Z 轴上的位置,值越大越靠近你(在上层),值越小越远离你(在下层)。

css
.layer-1 {
  position: relative;
  z-index: 1;
}

.layer-2 {
  position: relative;
  z-index: 2; /* 会在 layer-1 上面 */
}

.layer-3 {
  position: relative;
  z-index: 3; /* 会在 layer-2 上面 */
}

Z-Index 的基础规则

规则 1:只对定位元素有效

z-index 只对 position 不是 static 的元素生效:

css
/* ❌ z-index 无效 */
.element {
  z-index: 100;
}

/* ✅ z-index 有效 */
.element {
  position: relative; /* 或 absolute/fixed/sticky */
  z-index: 100;
}

规则 2:值可以是负数

css
.background-layer {
  position: relative;
  z-index: -1; /* 在其他内容下方 */
}

规则 3:值相同时,后来的在上面

css
/* HTML: <div class="box-1"></div><div class="box-2"></div> */

.box-1 {
  position: relative;
  z-index: 1;
}

.box-2 {
  position: relative;
  z-index: 1; /* 值相同,但 box-2 在 HTML 中更靠后,所以在上面 */
}

层叠上下文

层叠上下文(Stacking Context)是理解 z-index 最重要也最容易混淆的概念。

想象一个桌面上叠放的文件夹。每个文件夹就是一个层叠上下文,文件夹之间的上下关系是确定的。但文件夹里面的文件只能在这个文件夹内部比较上下关系,不能跨文件夹比较。

创建层叠上下文的方式

以下情况会创建新的层叠上下文:

css
/* 1. 根元素 html */

/* 2. position 为 absolute/relative 且 z-index 不为 auto */
.element {
  position: relative;
  z-index: 1; /* 创建层叠上下文 */
}

/* 3. position 为 fixed/sticky */
.element {
  position: fixed; /* 自动创建层叠上下文 */
}

/* 4. flex/grid 容器的子项,且 z-index 不为 auto */
.flex-container {
  display: flex;
}

.flex-item {
  z-index: 1; /* 创建层叠上下文 */
}

/* 5. opacity 小于 1 */
.element {
  opacity: 0.99; /* 创建层叠上下文 */
}

/* 6. transform 不为 none */
.element {
  transform: translateZ(0); /* 创建层叠上下文 */
}

/* 7. filter 不为 none */
.element {
  filter: blur(5px); /* 创建层叠上下文 */
}

/* 还有其他方式,如 mix-blend-mode、isolation 等 */

层叠上下文的影响

一旦元素创建了层叠上下文,它的子元素的 z-index 就只在这个上下文内部有效,不能跨上下文比较。

html
<div class="parent-1">
  <div class="child-1">Child 1 (z-index: 9999)</div>
</div>
<div class="parent-2">
  <div class="child-2">Child 2 (z-index: 1)</div>
</div>
css
.parent-1 {
  position: relative;
  z-index: 1; /* 创建层叠上下文 */
}

.child-1 {
  position: relative;
  z-index: 9999; /* 只在 parent-1 的层叠上下文内有效 */
}

.parent-2 {
  position: relative;
  z-index: 2; /* 创建层叠上下文,z-index 比 parent-1 大 */
}

.child-2 {
  position: relative;
  z-index: 1;
}

虽然 child-1 的 z-index 是 9999,但因为它的父元素 parent-1 的 z-index 是 1,而 parent-2 的 z-index 是 2,所以 parent-2(以及其子元素 child-2)会在 parent-1(以及其子元素 child-1)的上面。

这就像两个文件夹,A 文件夹在下面,B 文件夹在上面。即使 A 文件夹里有一张纸写着"我要在最上面",它也出不了 A 文件夹,所以还是在 B 文件夹下面。

Z-Index 的实际应用

1. 导航栏在内容上方

css
.navbar {
  position: fixed;
  top: 0;
  z-index: 1000; /* 确保在其他内容上面 */
}

.content {
  position: relative;
  z-index: 1;
}

2. 模态框层级

css
.modal-overlay {
  position: fixed;
  z-index: 9998; /* 遮罩层 */
}

.modal-content {
  position: relative;
  z-index: 9999; /* 内容在遮罩层上面 */
}

3. 下拉菜单在其他内容上方

css
.dropdown {
  position: relative;
}

.dropdown-menu {
  position: absolute;
  z-index: 100; /* 确保在其他内容上面 */
}

常见 Z-Index 问题

问题 1:设置了很大的 z-index 但不生效

原因可能是父元素创建了层叠上下文:

css
/* 问题 */
.parent {
  position: relative;
  z-index: 1; /* 创建了层叠上下文 */
}

.child {
  position: absolute;
  z-index: 99999; /* 只在 parent 的上下文内有效 */
}

/* 解决:调整父元素的 z-index */
.parent {
  position: relative;
  z-index: 100; /* 提高父元素的 z-index */
}

问题 2:z-index 无效

检查元素是否有定位:

css
/* ❌ 无效 */
.element {
  z-index: 100;
}

/* ✅ 有效 */
.element {
  position: relative;
  z-index: 100;
}

定位的实用技巧

1. 水平垂直居中

使用绝对定位实现完美居中:

css
.centered {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

为什么这样可以居中?因为 top: 50% 和 left: 50% 让元素的左上角位于容器中心,然后 transform: translate(-50%, -50%) 让元素自身向左上移动自身宽高的 50%,从而实现完美居中。

2. 全屏遮罩层

css
.overlay {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background-color: rgba(0, 0, 0, 0.5);
  z-index: 1000;
}

3. 响应式定位

在小屏幕上改变定位方式:

css
.sidebar {
  position: fixed;
  top: 0;
  right: 0;
  width: 300px;
  height: 100%;
}

@media (max-width: 768px) {
  .sidebar {
    position: static; /* 移动端恢复正常流 */
    width: 100%;
    height: auto;
  }
}

总结

CSS 定位是精确控制元素位置的强大工具:

五种定位方式:

  • Static:默认值,正常文档流
  • Relative:相对原位置偏移,保留原空间,常作定位参照
  • Absolute:脱离文档流,相对定位祖先定位,用于浮层、角标
  • Fixed:相对视口定位,不受滚动影响,用于固定导航、工具栏
  • Sticky:相对与固定的混合,用于粘性标题、侧边栏

Z-Index 要点:

  • 只对定位元素有效
  • 理解层叠上下文的概念
  • 子元素的 z-index 只在父上下文内有效

最佳实践:

  • Relative 通常不设置方位,只作为定位参照
  • Absolute 元素需要明确的定位上下文
  • Fixed 元素要注意是否遮挡内容
  • Sticky 要检查触发条件是否满足
  • 合理规划 z-index 层级,避免混乱

定位是 CSS 布局中非常实用的技术,掌握好定位可以实现很多传统布局难以实现的效果。但也要注意不要过度使用定位,因为定位元素脱离文档流,可能会给布局带来复杂性。在实际开发中,优先考虑使用 Flexbox 和 Grid 等现代布局方式,只在必要时使用定位。