position 有哪些取值?z-index 为什么有时不生效?
一句话回答
position 有五个值:static(默认,普通流)、relative(相对自身原位置偏移,仍占位)、absolute(相对最近的定位祖先,脱离文档流)、fixed(相对视口)、sticky(滚动到阈值前像 relative,之后像 fixed,但不会离开父元素)。z-index 不生效通常有两个原因:一是元素没有创建层叠上下文的条件(比如 static 元素直接写 z-index);二是z-index 只在同一个层叠上下文里比较,父元素的层级低,子元素 z-index 写得再大也盖不过父元素外面的兄弟。
详细解析
五种定位的参照物
| 值 | 是否脱离文档流 | 偏移参照 |
|---|---|---|
static |
否 | 不生效,top 等属性无效 |
relative |
否,原位置仍然占着 | 自己原来的位置 |
absolute |
是 | 最近的 position 不为 static 的祖先的 padding 区域;没有就是初始包含块 |
fixed |
是 | 视口;祖先有 transform 等属性时例外 |
sticky |
否 | 最近的滚动容器,活动范围限制在父元素内 |
absolute 和 fixed 的元素,如果某个祖先设置了 transform(包括独立的 translate、rotate、scale)、perspective、filter、backdrop-filter 不为 none,或者 will-change: transform、contain: layout / paint、content-visibility: auto 等,这个祖先就会成为它们的包含块。filter、perspective 在不同浏览器里的表现曾经不一致,遇到问题以实测为准。这就是 fixed 弹窗放在有 transform 的父元素里,不再相对视口定位 的原因。常见解决办法是把弹窗挂到 body 下,比如 Vue 的 <Teleport>、React 的 Portal。
sticky 为什么失效
- 没设阈值:必须至少设置
top、bottom、left、right中的一个 - 祖先有
overflow: hidden / auto / scroll:sticky 会把这个祖先当成滚动容器,如果真正滚动的是更外层的页面,就看不到效果。只想裁剪内容时可以改用overflow: clip,它不创建滚动容器 - 父元素高度不够:sticky 不会离开父元素,父元素和它一样高时就没有可"粘"的距离。典型情况是 flex 容器默认
align-items: stretch,侧边栏被拉得和父元素一样高,加上align-self: flex-start即可
层叠上下文
层叠上下文可以理解为一个独立的"图层组"。常见的创建条件:
- 根元素
html position为relative/absolute,且z-index不是autoposition为fixed/sticky(不需要 z-index)- flex / grid 的子项,且
z-index不是auto opacity小于 1transform、filter、perspective、clip-path、mask、backdrop-filter不为nonemix-blend-mode不为normalisolation: isolatewill-change指定了上面这些属性,contain为layout/paint等
在同一个层叠上下文里,从下到上的绘制顺序是:上下文自身的背景和边框 → z-index 为负的子元素 → 普通流的块级元素 → 浮动元素 → 行内内容 → z-index 为 auto 或 0 的定位元素 → z-index 为正的元素。
z-index 为什么不生效
- 元素不满足条件:普通流里
static的元素写 z-index 无效。flex / grid 子项是例外,即使是static也能用 z-index - 跨上下文比较:子元素的 z-index 只在父级层叠上下文内部有意义。父元素 A(
z-index: 1)里的子元素即使设成z-index: 9999,也永远在兄弟元素 B(z-index: 2)下面 - 无意中创建了层叠上下文:给父元素加了
transform动画、opacity过渡,或者will-change,就把子元素"关"在了里面
代码示例
<style>
.a { position: relative; z-index: 1; height: 60px; }
.b { position: relative; z-index: 2; margin-top: -40px; background: #bfdbfe; }
.a .popup { position: absolute; z-index: 9999; background: #fecaca; }
</style>
<div class="a">
A
<!-- 被 B 盖住:9999 只在 A 内部比较,A 整体的层级是 1 -->
<div class="popup">A 里的弹层</div>
</div>
<div class="b">B</div>
面试官可能追问
怎么排查 z-index 不生效?
从目标元素往上找祖先,看哪一层创建了层叠上下文,再比较这一层和"挡住它的元素"所在层的 z-index。Chrome DevTools 的 Layers 面板可以看合成层,但层叠上下文还是要靠检查祖先的样式。根本的办法是给弹窗、下拉菜单这类浮层统一挂到 body 下,并约定一套 z-index 层级。
isolation: isolate 有什么用?
它只做一件事:创建一个新的层叠上下文,没有任何视觉副作用。组件内部的 z-index 用它隔离后,就不会和外部元素互相干扰,比用 transform: translateZ(0) 或 z-index: 0 这类"偏方"更明确。
absolute 元素的百分比宽度相对谁?
相对它的包含块,也就是最近的定位祖先的 padding 区域。left: 50% 也是相对这个区域的宽度。这也是绝对定位居中写法的基础,见 水平垂直居中。
易错点
fixed不一定相对视口,祖先有transform、filter时会相对这个祖先opacity小于 1 也会创建层叠上下文,淡入淡出的父元素会把子元素的 z-index "困住"- sticky 不生效时先检查祖先的
overflow,以及父元素是不是被 stretch 成和自己一样高
AI 模拟面试官
用自己的话回答,AI 对照参考答案打分、指出遗漏,再追问,最多 3 轮
这道题你掌握了吗?
选一个最接近的状态,没掌握的题会出现在"我的进度 · 待复习"里。
学习记录暂存在本机浏览器。登录后自动同步到账号,换设备也能看到。