Skip to content
This page has been auto-translated and may contain errors.View in English

定位和 z-index

docs.scrimba.com

你想在产品图片的右上角放一个小的"促销"徽章。你想要一个在页面滚动时保持不动的页眉。你想要一个悬浮在页面上所有其他元素上方的对话框。普通的 CSS 间距无法做到这些,因为它只能一个接一个地排列元素。定位是一套工具,让元素脱离这种普通的排列方式,坐在你指定的位置,甚至在其他元素的上方。

position 属性

每个元素都以**static 定位**开始,这只是一种说法,即"坐在你在页面流中的正常位置"。这是默认情况,大多数情况下这正是你想要的。当你希望元素远离那个正常位置时,定位才开始起作用。

第一步是 position: relative。它使用四个偏移属性将元素从其正常位置移动:toprightbottomleft。关键是元素保留其原始空间。没有其他东西会移动来填补这个空隙。

css
.badge {
  position: relative;
  top: 10px;                 /* 从正常位置向下推 10px */
  left: 20px;                /* 从正常位置向右推 20px */
}

这些 topleft 偏移在 static 元素上完全不起作用。只有在你给元素设置 position 而不是 static 后,它们才开始工作。

页面上的所有内容都以 position: static 开始,它在你在显示和文档流中看到的正常从上到下的流中的位置。Static 元素会完全忽略偏移属性,所以在它们上设置 topleft 不会做任何事。这会让人困惑:他们添加了 top: 20px,没有看到变化,假设该值是错误的,而真正的问题是缺少 position

温和的选项是**相对定位**。它根据你给定的偏移从元素的正常位置移动元素,同时在流中保留其原始空间。邻近的元素不会移动来关闭空隙,所以相对定位的元素最终可能会与相邻的东西重叠。

css
.tooltip {
  position: relative;
  top: -4px;                 /* 从本来位置向上移 4px */
  left: 8px;                 /* 向右推 8px */
  /* 保留的空间保持不变;只有绘制移动 */
}

topleft 这样的偏移只在元素被定位后才应用。单独来看,relative 是一个小的轻推工具。它更重要的工作是为绝对定位的子元素建立参考点,这是下一部分。

position 属性有五个值,如果你按一个问题来分割整个模型会更容易理解:元素是否仍然在正常流中占用空间?static(初始值)和**relative** 都占用;absolutefixed 不占用;sticky 是一个混合体,在你滚动时在两种行为之间切换。

relative 是相对于 static 最温和的偏离。元素的布局完全和 static 时一样,它的空间在那个原始位置被保留,然后它通过 top / right / bottom / left 从该位置视觉上偏移。因为保留的空间永远不会移动,偏移相对定位的元素只会改变它的绘制位置,而不会改变布局认为它在的位置,所以它可以与相邻内容重叠而不会推动任何东西。

css
.list-item {
  position: relative;
  top: 6px;                  /* 绘制位置低 6px;它的流槽保持不变 */
}

两个精准的要点。首先,偏移是相对于元素自己的正常位置来解决的,而不是相对于任何祖先:top: 10px 意味着"比你原本位置低 10px",而不是"从容器顶部开始 10px"。其次,如果你在相对定位元素上设置相反的偏移(topbottom,或 leftright),在从左到右、从上到下的书写模式中,开始端获胜,所以 top 会覆盖 bottomleft 会覆盖 right。远比轻推更常见的是使用 relative 的原因是为 absolute 后代建立一个包含块,这是下一部分要深入的内容。

Junoposition 属性 一切都以 static 开始,意味着它坐在其正常位置。切换到 position: relative,你可以用 toprightbottomleft 轻推它,同时其原始空间保持保留。这些偏移属性在元素被定位之前不起作用,所以如果 top 似乎被忽略了,检查你是否先设置了 position
Junoposition 属性static 是默认值,完全忽略偏移,这就是为什么在普通元素上 top: 20px 不做任何事。relative 从元素的正常位置轻推它,同时保留其空间,所以邻近的元素不会移动来填补空隙。轻推很方便,但你使用 relative 的真正原因是锚定绝对定位的子元素,即将出现的内容。
Junoposition 属性 按照它们是否在流中保留空间对五个值进行分类:staticrelative 保留,absolutefixed 不保留,sticky 在两者之间切换。relative 偏移是相对于元素自己的正常位置来解决的,而不是其容器,相反的偏移让开始端获胜。你使用 relative 作为 absolute 子元素的锚点的频率远高于作为轻推工具。

绝对定位

position: absolute 是一个更大的移动。它将元素彻底拉出正常流,所以它不再占用任何空间,它周围的元素会合并,就像它从未存在过一样。然后你用相同的 toprightbottomleft 偏移来放置它。

问题是:相对于什么偏移?一个绝对定位的元素定位自己相对于它最近的**定位祖先**,意味着最近的有自己 position 的父元素(除 static 外的任何值)。这就是为什么卡片上的徽章模式总是成对出现的:你使卡片 relative 使其成为参考点,然后使徽章 absolute 使其可以坐在卡片的角落。

css
.card {
  position: relative;        /* 成为参考点 */
}

.card .badge {
  position: absolute;
  top: 8px;                  /* 从卡片顶部开始 8px */
  right: 8px;                /* 从卡片右边开始 8px */
}

如果卡片上没有 position: relative,徽章会在页面中继续向上搜索定位的祖先,并在你未打算的某处着陆。

绝对定位将元素完全从正常流中移除。它不占用空间,它的前邻居会合并,就像它已经消失一样,然后你用偏移属性来放置它。这些偏移是针对元素的包含块测量的:其最近的本身被定位的祖先的填充边缘。

这个"最近的定位祖先"规则是你将持续使用的模式的核心。将父元素设置为 position: relative(这本身不会改变任何可见的东西),它就成为其内部任何绝对子元素的参考框架。

css
.dropdown {
  position: relative;        /* 参考框架,没有视觉变化 */
}

.dropdown .menu {
  position: absolute;
  top: 100%;                 /* 直接在触发器下方 */
  left: 0;                   /* 与其左边缘对齐 */
  width: 200px;
}

如果没有祖先被定位,绝对定位的元素会回到初始包含块,大致是视口,通常最终会在页面的左上角。错放的绝对定位元素几乎总是在你打算锚定它的父元素上缺少 position: relative

绝对定位的元素被从正常流中取出:它在流中不生成框,对其父元素的高度没有贡献,其兄弟元素排列时就像它不存在一样。然后它相对于其包含块定位,准确掌握包含块规则是区分"它工作"和"它工作且我知道为什么"的关键。

对于 absolute 元素,包含块是其最近的 position 不是 static 的祖先的填充框。如果有多个祖先符合条件,最近的那个获胜。如果没有符合条件的,包含块就是初始包含块,一个视口大小的矩形,固定在文档上,这就是为什么不锚定的绝对定位元素会漂流到页面的左上角。

css
.field {
  position: relative;        /* 建立包含块 */
  padding: 12px;
}

.field .clear-button {
  position: absolute;
  top: 50%;
  right: 8px;
  transform: translateY(-50%);   /* 相对于 .field 垂直居中 */
  /* 偏移从 .field 的填充框测量,而不是其内容框 */
}

有一个规则让人们感到惊讶,对高级包含块图形而言很重要:祖先上的 transformfilterwill-change 也为绝对后代建立包含块,即使没有设置 position。所以绝对子元素可以突然重新父化其偏移到一个你没想到作为锚点的变换后的祖父母身上。当绝对定位元素相对于"错误的"元素定位时,检查树中是否有流浪的 transform,而不仅仅是 position

Juno绝对定位position: absolute 将元素拉出流,所以它不占用空间,邻近的元素会在后面合并。然后它相对于其最近设置了 position 的父元素定位。这就是为什么卡片上的徽章技巧是一对:父元素上的 relative,徽章上的 absolute,所以徽章着陆在卡片的角落,而不是某个随机的地方。
Juno绝对定位 绝对定位的元素完全离开流,相对于其包含块定位,即最近的本身被定位的祖先。日常模式是父元素上的 position: relative,这在视觉上不改变任何东西,但成为参考框架,然后子元素上的 absolute。当绝对定位元素着陆在页面的左上角时,意味着没有祖先被定位,它回到了视口。
Juno绝对定位 绝对定位元素的包含块是其最近的非 static 祖先的填充框,或者如果不存在则是视口。陷阱是 transformfilterwill-change 也会创建包含块,而看不到 position,所以子元素可以锚定到你忘记的被变换的祖父母。当偏移着陆在错误的元素上时,扫描树中是否有流浪的 transform,而不仅仅是缺少的 position

Fixed 和 sticky

position: fixed 将元素固定到屏幕本身。它像 absolute 一样离开流,但不是锚定到父元素,而是锚定到**视口**,即可见的窗口。一旦固定,即使你滚动页面,它也会保持在屏幕上的同一位置。这就是导航栏或"回到顶部"按钮如何始终保持可见的方式。

css
.site-header {
  position: fixed;
  top: 0;                    /* 固定到窗口的顶部 */
  left: 0;
  right: 0;                  /* 拉伸到完整宽度 */
}

position: sticky 是友好的中间地带。粘性元素在页面上正常滚动,直到到达你设置的阈值,然后它会停在该位置。随着其内容向上滚动然后卡在屏幕顶部的章节标题是经典示例。

css
.section-title {
  position: sticky;
  top: 0;                    /* 一旦到达顶部边缘就粘住 */
}

**固定定位**工作方式类似 absolute,除了包含块总是视口而不是祖先。固定定位的元素被移出流,然后固定到屏幕上的一个固定位置,所以当页面滚动时它不会移动。这使其成为持久 UI 元素的工具:顶部栏、cookie 横幅、浮动操作按钮。

css
.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 64px;
  /* 下面的内容需要 padding-top: 64px 所以它不会被隐藏 */
}

那条注释指出了主要陷阱。因为固定页眉不在流中,它与下面的内容重叠,所以你通常向页面体添加匹配的填充或边距来推动内容清晰。

粘性定位是一个混合体:元素在视图内时表现得像 relative,然后一旦它触及你用 topbottomleftright 设置的阈值,它就切换到类似 fixed 的行为。没有这些阈值偏移之一的粘性元素永远不会粘住,因为它没有要粘的线。

css
.section-title {
  position: sticky;
  top: 16px;                 /* 一旦到达那里,就保持在距顶部 16px */
}

fixedabsolute,但有一个重要的不同:其包含块是视口,所以它固定到屏幕上,不会与文档一起滚动。这使其适合持久 UI,有两个注意事项。它与流内内容重叠(用body 上的填充保留空间),并且来自 absolute 的相同 transform 建立包含块规则也在这里适用,所以祖先 transform 会将 fixed 元素困在该祖先内,它会随之滚动。一个神秘滚动的"固定"元素几乎总是坐在被变换的祖先下。

**粘性定位**是五个中最有条件的。粘性元素在正常流中布局,保留其空间,并停留在那里,直到其最近的滚动祖先将其滚动到你声明的阈值;从那时起,它被保持在阈值处,直到其包含块滚动过,此时它释放。两个要求是人们错过的:

css
.toc {
  position: sticky;
  top: 24px;                 /* 阈值;没有偏移就永远不会粘住 */
  /* 只在 .toc 的父元素仍在屏幕上时粘住 */
}

首先,sticky 需要一个阈值偏移(topbottomleftright);没有,就没有线可以吸附,它表现得像普通的相对元素。其次,它只能在其父元素的边界内粘住,所以如果父元素很短,一旦父元素滚动到视图外,元素就会取消粘住。还有一个常见的无声失败:祖先有 overflow: hiddenscrollauto 就成为粘性元素的滚动容器,所以粘性项目可能会安静地什么也不做,因为它被限制在一个你没预料到会滚动的容器内。

JunoFixed 和 stickyposition: fixed 将元素固定到屏幕,所以当你滚动时它保持不动,这就是页眉或回到顶部按钮如何保持可见的方式。position: sticky 是中间地带:它随页面滚动,然后一旦到达像 top: 0 这样的阈值就粘住。Fixed 也会与下面的东西重叠,所以为它覆盖的内容留出一些空间。
JunoFixed 和 stickyfixed 锚定到视口,所以当页面滚动时它保持在屏幕上,代价是与流内内容重叠,除非你在周围填充。sticky 表现得像 relative,直到它触及其阈值偏移,然后保持在位置。没有设置 topbottom 的粘性元素永远不会粘住,因为没有线让它吸附。
JunoFixed 和 stickyfixed 固定到视口,但祖先 transform 会将其困在该祖先内,它会滚动,所以一个滚动的"fixed"元素通常有一个被变换的父元素。sticky 需要两个人们忘记的东西:一个阈值偏移和一个足够高的父元素,因为一旦其父元素滚动出,它就会释放。还要注意祖先的 overflow 设置,它成为滚动容器,可以无声地停止 sticky 工作。

使用 z-index 堆叠

当元素重叠时,必须有东西来决定哪个坐在上面。这就是 z-index 控制的。较高的 z-index 在较低的前面绘制,所以 z-index: 10 的下拉菜单覆盖 z-index: 1 的卡片。

有一个规则会绊倒所有人:z-index 只适用于定位的元素。如果元素仍然是 static,其 z-index 会被忽略。所以你几乎总是同时设置 positionz-index

css
.modal {
  position: fixed;
  z-index: 100;              /* 坐在普通页面内容之上 */
}

.modal-overlay {
  position: fixed;
  z-index: 99;               /* 黑色背景,模态窗口后面一步 */
}

你很少需要这里的大数字。一个小的、整洁的比例,如 110100,比充满 9999 相互争斗的页面更容易理解。

当两个元素重叠时,z-index 设置其前后顺序:较高的数字在较低的前面绘制。陷阱是**z-index 只影响定位的元素**。给 static 元素一个 z-index,什么都不会发生,所以 z-indexposition 几乎总是一起出现。

css
.modal {
  position: fixed;
  inset: 0;
  z-index: 100;
}

.tooltip {
  position: absolute;
  z-index: 50;               /* 在卡片上方,模态窗口下方 */
}

值得尽早形成的习惯是一个小的、有意的比例,而不是不断增加的数字。当你发现自己在写 z-index: 9999 来击败某些东西时,真正的修复通常是对你的堆叠值的计划,而不是更大的数字。下一个级别解释为什么更大的数字有时仍然会输。

z-index 沿 z 轴排列重叠的定位元素,较高的在前,在 static 元素上被忽略。将五分钟的错误变成一小时的部分是堆叠上下文:一个自包含的组,其中 z-index 值被比较,并作为一个单元与其兄弟元素堆叠。

每当一个元素以 z-index 而不是 auto 定位时,就会形成堆叠上下文,但也来自于 opacity 小于 1、任何 transformfilterwill-changeisolation: isolate,以及一些其他。至关重要的是,z-index 仅在相同堆叠上下文被比较。一旦元素坐在上下文内,其 z-index 无法将其提升到该上下文之外的任何东西之上,无论数字有多大。

css
.card {
  opacity: 0.98;             /* 这单独就创建了一个堆叠上下文 */
}

.card .badge {
  position: absolute;
  z-index: 9999;             /* 被困在 .card 的上下文内 */
}

.sibling-panel {
  position: relative;
  z-index: 2;                /* 仍然在 .badge 之上绘制 */
}

这里 .badge9999 输给了 .sibling-panel2,因为 .badge 被限制在 .cardopacity 创建的上下文内,而整个上下文在 .sibling-panel 下面排名。当一个高 z-index 拒绝赢时,答案几乎从不是一个更高的数字;它是一个悄悄打开堆叠上下文的祖先(通常是 opacitytransform),修复是提升或隔离那个祖先。

关于判断的最后一点:定位和 z-index 是用于覆盖层、徽章和固定 UI,那些需要逃离流或坐在顶部的东西。它们是排列页面主要结构的错误工具。对于应该响应其内容和视口的行、列和网格,使用 flexboxbox 模型中的由 box 模型驱动的流,并为浮动在它们之上的图层保留定位。

Juno使用 z-index 堆叠 当元素重叠时,z-index 决定哪个在前面:较高的数字赢。它只适用于定位的元素,所以普通 static 元素上的 z-index 被忽略,你同时设置 positionz-index。保持数字小而整洁,如 110100,而不是一堆 9999
Juno使用 z-index 堆叠z-index 为定位元素设置前后顺序,对 static 的没有作用,所以它随 position 一起出现。使用小的有意的比例而不是爬升的数字;如果你在写 9999 来赢,你需要一个计划,而不是一个更大的值。有时一个更大的值仍然会输,深入堆叠上下文的解释了这一点。
Juno使用 z-index 堆叠z-index 仅在堆叠上下文内被比较,而像 1 以下的 opacitytransformfilter 这样的东西会悄悄创建一个。这就是为什么 z-index: 9999 的子元素仍然会输给外部元素 2:它被困在其祖先的上下文中。当一个高数字不会赢时,寻找打开上下文的祖先并提升那个,并为覆盖层保留定位,而不是页面布局。