position: sticky 不吸顶时,我按这个顺序查

sticky 的行为限定得很死:元素在最近的滚动祖先里滚动,位移范围限制在它的父容器内。这两句话就决定了绝大多数”失效”的原因。

1. 有没有写偏移值

position: sticky 单独写是没作用的,必须有 top / bottom / left / right 至少一个。这是唯一一条纯粹的低级错,但确实常见。

2. 祖先链上的 overflow(最高频)

只要有一个祖先的 overflow 不是 visible,元素就只在那层容器里粘。而很多 overflow 是”为了别的问题”顺手加的:

  • 为了清浮动加的 overflow: hidden;
  • 为了去掉横向滚动条,在 body 或 html 上写 overflow-x: hidden——这条杀伤力最大,因为它离得远,谁都想不到;
  • 卡片截断文本加的 overflow: hidden;
  • 某些 UI 库在容器上默认 overflow: auto。

逐个往上翻 computed 太慢,直接让浏览器自己报:

let el = document.querySelector('.sticky-target');
let n  = el && el.parentElement;
while (n && n !== document.documentElement) {
  const s = getComputedStyle(n);
  if (s.overflowX !== 'visible' || s.overflowY !== 'visible') {
    console.warn('这层会限制 sticky:', n, s.overflowX + ' / ' + s.overflowY);
  }
  n = n.parentElement;
}

控制台里跳出来的第一个,八成就是凶手。

3. 父容器和元素一样高

sticky 的移动范围是父容器的内容区。父容器高度等于元素高度时,可移动距离是 0,看起来就是”完全没生效”。查法:选中元素,看它在 DevTools 里的 size 和父级差多少,差值为 0 就别往下查了。

4. 父级是 flex,且 align-items 是默认值

flex 容器的 align-items 默认 stretch,交叉轴上子项会被拉到和容器等高。于是又回到第 3 条:父容器多高,元素就多高,没有可移动空间。

.wrap { display: flex; align-items: flex-start; }  /* 让子项按自身高度 */
/* 或者只针对这一项 */
.sticky-target { align-self: flex-start; }

5. 在表格里

<thead> 做粘性表头,Chrome 上一般能行,Safari 上行为不稳。稳妥的写法是给 th 上 sticky,并且去掉 border-collapse: collapse(改 separate 配合 border-spacing: 0,边框改用 box-shadow 模拟),否则 sticky 表头的边框容易消失。

6. 高度链条是”百分比悬空”

祖先写了 height: 100%,但再往上没有确定高度的祖先,这个 100% 会退化成 auto——父容器高度由内容撑开,sticky 的计算基准随之变化,表现就是”整页都在滚、元素纹丝不动”。用 min-height: 100vh 或 height: 100dvh 之类有明确值的高度替代,链条才立得住。

排查顺序为什么是这个顺序

因为概率。第 2 条(overflow 祖先)我遇到的次数比后面五条加起来还多,尤其是那句远在 body 上的 overflow-x: hidden。所以先用那段脚本扫一遍祖先,扫不出来再回头做理论分析——比一上来就翻规范快。

← 返回技术笔记