给后台加了个吸顶侧栏,本地好好的,一到测试环境就不粘。打开控制台:没有报错,没有警告,position: sticky; top: 0 白纸黑字写在那。我改半小时,改成 relative 又改回来,开始怀疑浏览器。
后来不修了,改搜教程。搜出一堆「sticky 失效的 N 种原因」,说法五花八门还互相打架------有人赖 transform,有人赖 border-collapse,有人直接把 overflow 全家判了死刑。行,那我一个一个验。
判据跑前定死:滚动 250px 后,元素到滚动容器可视顶的偏移,钉在阈值 ±3px 内算粘住,Chrome 实测。先看判决:
| 网传说法 | 判定 | 实测真相 |
|---|---|---|
祖先 overflow: hidden,sticky 失效 |
真死 | hidden 劫持了滚动参照系 |
| 滚动容器和内容等高,失效 | 真死 | 没有滚动空间 |
| sticky 是 flex 子项会失效 | 真死 | stretch 拉满,无处可动 |
| 不写 top 不粘 | 真死 | 没阈值=普通定位 |
border-collapse 表头失效 |
只对一半 | 几何粘得住,死的是边框 |
祖先 overflow: clip 也失效 |
不成立 | clip 是解药 |
祖先 transform 失效 |
不成立 | 它只劫持 fixed |
7 条说法:3 条不成立,1 条只对一半,4 条真死。而且真死的 4 种控制台全程安静------sticky 这东西,疼了都不喊一声。下面按坑度排。
头号凶手:爷爷辈的 overflow: hidden
最经典的现场:页面滚动好好的,sticky 的某个祖先------轮播外壳、弹窗遮罩、日历浮层------写了 overflow: hidden,吸顶当场失灵。
机制一句话:hidden 也是一个滚动容器,只是你滚不动它。sticky 认滚动祖先只认「最近的」那个,一睁眼把 hidden 层认了------爹自己不滚,它就永远跟着内容走。实测滚动 250px 后,偏移从 126px 变成 -124px,跟得严丝合缝。
sticky 没坏,是被抱错了房间:一心想钉在窗户上,结果被关进一个不动的屋子。
哪里会藏一个 hidden?轮播组件、Modal 遮罩、日历浮层、UI 库的容器组件------好多自带 hidden 裁圆角。sticky 本体没事,是头顶上某个看不见的祖先替它做了决定。
修复两选一:hidden 只是为了裁剪的,换 overflow: clip,后面有对照实验;必须保留 hidden 的,把 sticky 挪出这个祖先,别跟它做亲戚。
死法二和三,其实是同一把锁
等高容器。 sticky 只能在父块范围内活动,父块要是和滚动空间一样高------实测 maxScroll = 0,连滚都滚不起来,粘什么粘。
flex 子项被 stretch。 这个更阴:侧栏自己就是 flex 子项,align-items 默认 stretch 把它拉成和主列一样高。约束块多高它就多高,活动空间归零。实测偏移 2px → -248px,直接滚没影。
html
<div style="display: flex;">
<div style="flex: 1; height: 800px;">主列</div>
<div style="position: sticky; top: 0;">侧栏</div> <!-- 被 stretch 拉成 800px -->
</div>
修复就一行:align-self: flex-start,让它塌回自己的高度。实测偏移立刻钉在 2px。中后台侧边栏最常踩这个------左长右短是标配布局。
两件事一个本质:父块和滚动空间一样高,sticky 就没有相对移动的余地。你没想到锁它,flex 替你锁了。
忘写 top:就少一个单词
position: sticky 不带 top(或任意阈值),浏览器当它是 relative。语法合法,控制台安静,就是不粘。排查一晚上,答案是少写一个单词,气人程度五颗星。
css
.sidebar { position: sticky; } /* 等于没写 */
.sidebar { position: sticky; top: 0 } /* 阈值是开关 */
规范原文就这么定的:没有阈值,「钉在哪」就没有定义,退回普通定位。不算坑,算语义------但你得知道。
表格表头:网上说它死,它其实活着
这条先纠错。大量文章说「border-collapse: collapse 会让 th 的 sticky 失效」------实测它粘得住。对照实验 collapse 和 separate 两个表格,th 滚动后偏移都稳稳钉在 2px,几何上没有任何区别。
真正死的是边框。collapse 模式下边框归表格不归单元格:th 被钉在顶上,下边框却跟着表格滚走了,表头和第一行粘成一片。功能活着,视觉死了。这是 Chromium 的已知问题(issue 329702833),Firefox 有同款记录(bugzilla 1727594)。
修复:border-collapse: separate; border-spacing: 0,边框还给单元格。
数据表格是 sticky 的最大用武之地。后台、报表、对账单,都该自查一遍:表头吸住了,第一行的边框还在吗?
两起冤案,当场释放
「祖先有 transform,sticky 就失效。」 前半句对,后半句错。transform 劫持的是 fixed 的参照系,sticky 认的是滚动祖先,管不着。实测祖先挂 translateZ(0),sticky 纹丝不动。这条我跑前就放话:真失效我就弃题不写。结果它粘住了。顺带 will-change: transform 也一样没事。
「overflow 家族都危险。」 恰恰相反,clip 是解药。同样的结构,祖先从 hidden 换成 clip,sticky 吸得死死的------clip 是纯裁剪,不创建滚动容器,sticky 直接跳过它找下一层。2022 年起浏览器支持全绿。修复口诀记这条:裁剪用 clip,别用 hidden。又避坑又救命,全文最值得带走的就是它。
有人会说
「我项目里全是 hidden,sticky 好好的。」------你的 sticky 和 hidden 之间隔了真正的滚动层,它认的是外层,hidden 无罪。但这雷一直在:哪天调整 DOM 把中间那层拆了,hidden 当场顶上。
「不修 sticky,JS 监听 scroll 加 fixed 行不行?」------能跑。代价自己扛:scroll 高频触发要节流、fixed 脱离文档流要补占位、滚动嵌套要重算参照系。三行 CSS 能解决的事,别上 30 行 JS。
「Safari 呢?」------本次是 Chrome 单浏览器几何判据,Safari 未测。表格边框那条 Firefox 有官方记录,其余欢迎 Safari 用户评论区补充实测,先谢过。
速查表,直接存
| 症状 | 先查 | 修复 |
|---|---|---|
| 完全不粘 | 祖先链有无 hidden/auto/scroll | 换 clip,或挪出去 |
| 完全不粘 | 是不是 flex/grid 子项 | align-self: flex-start |
| 完全不粘 | 写没写 top | 补阈值 |
| 粘了被滚走 | 父块是不是和内容等高 | 拉开高度差 |
| 吸了没边框 | 是不是 border-collapse: collapse |
换 separate |
DevTools 三步收尾:Elements 面板从 sticky 往上爬,遇到第一个 overflow 非 visible 的节点,就是它认的滚动爹;对比这个爹的内容高度和 sticky 父块的高度,一样高就是等高死法;Computed 看 top,空的补上。
最后统计一下:你们项目里现在还躺着几个没写 top 的 sticky?评论区举个手。踩过第 5 种之外死法的更好,留下复现步骤,攒够了我开续集收编。