CSS 教程 / CSS position:relative、absolute、fixed 与 sticky

position 属性改变盒子的定位方式以及其在正常流中的参与方式。relative 保留原始空间,absolute 和 fixed 离开正常流,sticky 保留正常流占位,并在滚动达到指定阈值时吸附,但受包含块边界限制。

将定位模式与 static 进行比较

值 正常流的原始空间 位置和滚动标准
static 保留 一般流。像 top·left 这样的偏移量不适用
relative 保留 从原始放置位置移动偏移量
absolute 未占用 通常是最近已定位的祖先的包含块。
fixed 未占用 通常以视口为基准;某些祖先属性可能改变这个基准
sticky 保留 附近滚动机制的祖先和包含块的边界

正常流是指元素根据文档的格式规则占据空间的布局方式。跟在绝对定位元素后的文本不会自动为其让位。当一个元素脱离流时,后续内容可以占据它之前的位置。通过布局结构或适当的内边距防止重叠。比较定位与CSS的 display: Block、Inline、inline-block 和隐藏,以理解这种差异。

偏移量取决于定位模式。使用相对定位时,top: 16px 将盒子从原始位置下移16px。使用绝对定位时,top 是从其包含块参照来测量的。因此,相同的数值表示不同的定位关系。参见 MDN:position。

用 relative 保留原始占位

relative 不会重新分配其他元素的位置。即使你稍微向下移动一张卡片,下一张卡片的原始起始位置仍然保持不变。所以它可能会与下面的元素重叠,或在上方显示空白。如果目的是改变整个文档的间距,首先检查边距、内边距和间隙是否合适,而不是仅仅使用 relative top。

relative 的另一种用途是为子元素创建绝对定位的参考。如果你只在父元素上写 position: relative 而不指定偏移,父元素本身不会移动,并且会成为子徽章的参考。指定位置并不意味着所有元素都会立即重叠或脱离文档流。

查找绝对定位的包含块

绝对定位的元素通常是相对于其最近的非静态祖先定位的。包含块(containing block)是定位偏移和相关尺寸计算的参考区域。如果给卡片设置 position: relative,并给子元素设置 position: absolute、top 和 right,它就会成为卡片内的徽章。如果没有相应的祖先,则会使用更外层的标准,这可能导致徽章移动到页面而不是卡片。transform 等其他属性也可能建立包含块,因此排查时不能只查找 position 声明。

一个只有绝对定位子元素的父元素不会将子元素的高度加到正常流中的内容高度中。即使子元素可见,也可能出现父元素的内容高度为零、边框未包围可见子元素的问题。如果内容需要自然决定父元素的高度,通过将子元素放置在正常流中,并仅将小的重叠徽章设置为绝对定位,更容易管理。

比较正常流和绝对定位徽章

将以下代码保存为 position-flow.html,以 UTF-8 编码保存并在浏览器中打开。三个部分由前元素、目标和后元素按相同的 HTML 顺序组成。徽章的 top 和 right 均为 12px,相对于卡片建立的包含块定位。卡片另留出 64px 顶部内边距,避免本例的徽章覆盖正文。

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>position-flow.html</title>
  <style>
* { box-sizing: border-box; }
body { margin: 16px; font: 16px/1.6 sans-serif; color: #243447; }
h1 { font-size: 24px; } h2 { font-size: 20px; margin-top: 32px; }
a { color: #145f8f; } a:focus-visible { outline: 3px solid #b4470b; outline-offset: 3px; }
.flow { border: 2px dashed #789; margin-bottom: 40px; margin-right: 24px; }
 .flow > div { height: 56px; padding: 12px; background: #e8f5de; }
 .flow .target { background: #dceefa; }
 .relative .target { position: relative; top: 16px; left: 20px; }
 .absolute { position: relative; }
 .absolute .target { position: absolute; top: 16px; left: 20px; }
 .card { position: relative; padding: 64px 20px 20px; background: #dceefa; max-width: 600px; }
 .badge { position: absolute; top: 12px; right: 12px; padding: 4px 12px; background: #ffe4b5; }
  </style>
</head>
<body>
<h1>Does the original space remain?</h1>
<h2>static</h2><div class="flow static"><div>Front element</div><div class="target">Target</div><div class="after">Back element</div></div>
<h2>relative</h2><div class="flow relative"><div>Front element</div><div class="target">Target</div><div class="after">Back element</div></div>
<h2>absolute</h2><div class="flow absolute"><div>Front element</div><div class="target">Target</div><div class="after">Back element</div></div>
<h2>Badge inside the card</h2><div class="card"><span class="badge">News</span><p>The badge sits at the top right of the card. Top padding separates it from the text.</p></div>
</body>
</html>
  1. 比较静态和相对部分中以下元素的位置。即使相对目标向下移动16px,后续元素的放置空间仍相对于原始目标位置保持。通过仅将top改为0然后再恢复为16px进行确认。
  2. 绝对定位部分会将目标元素移出文档流,并将后续元素紧接在前一个元素之后放置。如果将 .absolute .target 的 position 改为 static,父元素的高度会增加目标元素的高度。比较后,再恢复为absolute。
  3. 尝试仅删除.card中卡片徽章的position: relative。确认徽章不再固定在卡片右上角后,立即恢复它。其他条件不变。
  4. 拉长徽章上的字母,并检查它们与主文本之间的间距。absolute不会自动推开主体,因此如果你的徽章可能有多行,则应考虑仅靠固定的内边距是否足够。练习后,恢复原来的字母。

区分固定(fixed)和粘性(sticky)滚动

fixed 通常锚定到视口,即使文档滚动,它也能保持在屏幕上的位置。它可以用于小型操作元素,例如本例右下角的 Back to top 返回顶部链接。它不会占用布局流中的空间,因此如果有一个 fixed 栏,请确保留出足够的空间,以免遮挡正文末尾和焦点对象。

祖先的 transform 可以为固定子元素建立包含块,取代通常的视口参考。如果固定元素随某个特定盒子移动,请检查祖先属性,如 transform 和 filter。本示例排除了这些属性,以展示视口定位。

sticky 需要在吸附轴上设置至少一个不为 auto 的偏移值,例如 top: 0,才能观察到吸附效果。标题一开始并不会固定在屏幕上,而是保持在自身的布局位置,当通过滚动达到上边界时才会附着。当它到达父元素的末端时,会沿着边界移动,而不会继续悬浮在下一个部分上方。

sticky 会受到其最近滚动机制祖先元素的影响。祖先的 overflow: auto、scroll 或 hidden 都可能建立相关滚动机制,即使该祖先本身并没有实际发生滚动。如果实际滚动区域与预期的祖先元素不同,或者容器和标题的高度接近且没有移动空间,粘附行为可能不可见。与其仅仅移除溢出,不如先检查哪些元素是基于滚动的。

观察粘性起始位置和父级限制

将示例保存为 position-scroll.html,以 UTF-8 编码保存并在浏览器中打开。一个至少 320px 高的引导区域可以使粘性标题保持在初始视口顶部下方。本章和后续的节有足够的高度,以观察何时开始粘性以及何时父级边界释放它。

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>position-scroll.html</title>
  <style>
* { box-sizing: border-box; }
body { margin: 16px; font: 16px/1.6 sans-serif; color: #243447; }
h1 { font-size: 24px; } h2 { font-size: 20px; margin-top: 32px; }
a { color: #145f8f; } a:focus-visible { outline: 3px solid #b4470b; outline-offset: 3px; }
body { margin: 0; padding-bottom: 80px; }
 .intro { min-height: 320px; padding: 24px; background: #eef2f6; }
 .chapter { min-height: 760px; background: #e8f5de; }
 .sticky { position: sticky; top: 0; margin: 0; padding: 16px; background: #dceefa; border-bottom: 2px solid #789; }
 .chapter p { padding: 16px; }
 .tail { min-height: 900px; padding: 24px; background: #fff2df; }
 .fixed { position: fixed; right: 16px; bottom: 16px; padding: 12px; background: #fff; border: 2px solid #145f8f; }
  </style>
</head>
<body>
<div class="intro" id="top"><h1>A heading that sticks while scrolling</h1><p>The heading below starts in normal flow. It sticks when it reaches the top of the screen and remains within its parent at the end of the chapter.</p></div>
<section class="chapter"><h2 class="sticky">Chapter heading</h2><p>This content is inside the chapter. The extra height lets you observe how long its heading remains sticky.</p></section>
<section class="tail"><h2>Another section</h2><p>Check whether the previous chapter's heading stays above this section.</p></section>
<a class="fixed" href="#top">Back to top</a>
</body>
</html>
  1. 在滚动之前,确保标题在引导区域下方。慢慢向下滚动页面,以便标题在到达屏幕顶部时固定在位置。右下角的顶部链接从一开始就在相同的屏幕位置。
  2. 滚动浏览整章,然后下移到其他区域。粘性标题会被推到父章节的末尾,但固定链接仍然可见。点击链接以返回引导区域。
  3. 如果你只将 .sticky 的 top 改为 auto,垂直吸附条件将被取消,标题随正常流滚动;原有 border-bottom 不会因此移除。确认无误后,再恢复为 0。
  4. 只需在你的 .chapter 上添加 overflow: hidden,观察最近滚动机制祖先改变后,标题在窗口滚动时是否还会吸附在视口顶部。比较后,删除添加的声明。

诊断意外定位

如果即使指定了 position 堆叠顺序仍未改变,请检查 CSS 的 z-index 和堆叠上下文。

  1. 首先,检查元素是否保留正常流占位。周围元素的放置会因其是 relative/sticky 还是 absolute/fixed 而有所不同。
  2. 检查开发者工具中实际的 position 以及 top、right、bottom、left 的值。同时检查哪些规则和媒体查询具有优先权。
  3. absolute 检查基本祖先,fixed 检查改变包含块的祖先属性。sticky 检查边界、滚动祖先、父元素高度和可供移动的空间。
  4. 如果坐标是正确的但被遮挡,区分位置问题和重叠顺序/溢出截断问题。较大的 z-index 并不能解决所有遮挡。