先记住这个答案
CSS 规范规定,如果一个元素设了 transform 属性(值不是 none),它就会成为后代元素定位时的包含块。对 position: absolute 来说,这只是多了一个可能的定位祖先,影响不大。但对 position: fixed 来说,它原本相对视口定位,现在变成相对这个祖先定位,滚动时会跟着祖先一起走,看起来就像 fixed 失效了。
transform可改变后代fixed包含块filter等属性也可能有类似效果absolute不受影响,它本来就找最近的定位祖先- 把浮层移出变换父级可恢复视口定位
一个最常踩坑的场景
做弹窗(modal)时经常遇到这个问题。弹窗用 position: fixed 铺满全屏,但它的父组件碰巧加了 transform 做动画,弹窗就不再铺满视口了。
/* 父元素加了 transform 做入场动画 */
.page-wrapper {
transform: translateZ(0); /* 哪怕只是用来触发 GPU 加速 */
}
/* 子元素想全屏覆盖 */
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
/* 实际上相对 .page-wrapper 定位,不是视口 */
}打开 DevTools 审查 .modal,会发现它的包含块不是 <html> 而是 .page-wrapper。宽高 100% 算出来是 .page-wrapper 的尺寸而非视口尺寸。
translateZ(0) 经常被当作「开启 GPU 加速」的小技巧加到容器上,结果把后代的 fixed 全搞坏了。
哪些属性会触发同样的效果
不只是 transform。CSS 规范列出了好几个会创建新包含块的属性。
filter(值不是 none)会触发。有些项目给滚动容器加 filter: blur(0) 做模糊切换,结果发现里面的 fixed 元素也跟着动了。
will-change 的值是 transform 或 filter 时也会触发。即使元素还没有真正设 transform,光声明 will-change: transform 就够了。
perspective(值不是 none)同样如此。contain: paint、backdrop-filter 和 content-visibility: auto 也在规范的触发列表里。
碰到 fixed 定位异常,第一步就是沿着 DOM 树往上找,看有没有祖先设了以上任何一个属性。
怎么解决
最可靠的办法是把 fixed 元素移到设了 transform 的元素外面,让它不再是后代。在 React 里可以用 createPortal 把弹窗挂到 document.body 上。
如果 DOM 结构改不了,可以考虑去掉祖先上不必要的 transform。很多时候 translateZ(0) 只是习惯性地加上去的,去掉对性能没有实际影响。
用 JavaScript 获取视口宽高然后设给 fixed 元素也能绕过这个问题,但维护成本高,不推荐。
容易答错的地方
- 以为
absolute也会受影响 position: absolute本来就是相对最近的定位祖先(设了position的祖先)来定位的。transform让元素成为包含块后,absolute子元素会参照它,但这通常正好是你期望的行为,不算「失效」。- 以为只有
transform会触发 filter、will-change: transform、perspective、contain: paint都会触发同样的效果。排查问题时只看transform会漏掉真正的原因。
面试官还会怎么问?
为什么 translateZ(0) 常被用来开启 GPU 加速?
浏览器发现元素有 3D 变换时,通常会把它提升到独立的合成层,在 GPU 上渲染。translateZ(0) 虽然实际上不移动元素,但触发了这个优化。不过现在浏览器的合成策略更智能了,盲目加 translateZ(0) 反而可能增加内存消耗和 fixed 定位问题。
React 弹窗为什么推荐用 createPortal?
createPortal 把弹窗的 DOM 节点挂到 document.body 下面,脱离了组件树的 DOM 层级。这样弹窗就不会受任何祖先的 transform、overflow: hidden、z-index 等属性的影响。React 的事件冒泡依然遵循组件树,不会因为 DOM 位置变了就断掉。
给一个 fixed 元素加了 transform 动画,它自己会怎样?
fixed 元素自己设了 transform 不会影响自己的定位,它还是相对视口。但如果它有 fixed 后代,那这个后代的包含块就变成了它,和上面说的是同一个规则。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。