我验了 7 种 sticky 失效说法:3 种根本不成立

0 阅读6分钟

给后台加了个吸顶侧栏,本地好好的,一到测试环境就不粘。打开控制台:没有报错,没有警告,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,直接滚没影。

<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。语法合法,控制台安静,就是不粘。排查一晚上,答案是少写一个单词,气人程度五颗星。

.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 种之外死法的更好,留下复现步骤,攒够了我开续集收编。