学完这篇你能准确判断一个「吸顶效果」该不该用 position: sticky、为什么有时候写了却不生效,并且能自己动手排查出原因。
第一步:先确认 sticky 的三个硬性前提
position: sticky 不是「写了就粘」,它同时要满足三个条件才生效:
.article-title {
position: sticky;
top: 0; /* ① 必须指定方向阈值,缺了就退化成 relative */
z-index: 10; /* ② 粘住后要压住后面的内容 */
background: #fff; /* ③ 不透明背景 */
}
- 必须写
top / bottom / left / right 中至少一个,它是「距离滚动容器边缘多少像素时开始粘」的阈值,不写就等于没开启。
- 父容器必须比它高,元素才有滑动空间。父容器高度和它一样,它根本没机会移动。
- 粘性元素相对于「最近的滚动祖先」定位,不是相对
body。这点是后面所有坑的根源。
注意:top: 0 表示元素顶部碰到滚动容器顶部时粘住;top: 20px 表示还差 20px 就粘住,常用于给固定导航栏留位。
第二步:四种典型使用场景
1)表格表头吸顶
.table-wrap { max-height: 500px; overflow: auto; }
.table th {
position: sticky;
top: 0;
background: #fff;
z-index: 2;
}
这里 .table-wrap 就是那个「滚动祖先」,sticky 是相对它生效的。
注意:表格用 border-collapse: collapse 时,sticky 单元格的边框常会消失。改用 border-collapse: separate; border-spacing: 0;,或者用 box-shadow: inset 0 -1px 0 #ddd 画线。
2)侧边栏目录 / 广告位吸顶
.layout {
display: flex;
align-items: flex-start; /* 关键 */
}
.sidebar {
position: sticky;
top: 80px;
align-self: flex-start; /* 双保险 */
}
3)长文里的粘性日期或分类标签,滚动到该段落时顶上,划过去自动被下一段推走——这是 sticky 相比 fixed 最大的优势:它保留占位、并且被父容器边界约束,不会一路跑到页面底部。
4)底部操作条
.bottom-bar { position: sticky; bottom: 0; }
父容器滚到底部前它一直贴在视口底部,滚完就归位。
第三步:六个让 sticky 失效的坑
- 祖先有
overflow: hidden / auto / scroll。它会成为新的滚动容器,元素只在这个小盒子里粘,看起来就像没生效。
注意:overflow-x: hidden 也会让 overflow-y 的计算值变成 auto,所以给 body 加 overflow-x: hidden 是常见的「吸顶突然失灵」元凶。
- Flex / Grid 子项被拉伸。默认
align-items: stretch 会把子项拉满父容器高度,元素和父容器一样高,没有滑动空间。加 align-self: flex-start 解决。
- 祖先有
transform / filter / perspective / will-change: transform。这些属性会创建新的包含块,改变粘性参照物,表现是「粘的位置莫名其妙」。排查时把这些属性挪到别的元素上。
- 父容器高度不够。父容器和子元素一样高,粘性无从谈起。
z-index 太低,粘住了但被后面的内容盖住,视觉上以为没生效。
- 没有背景色,粘住后下面滚过的文字从它身上透出来,看着像坏掉。
第四步:怎么快速定位原因
在浏览器控制台跑这段脚本,把选择器换成你的元素,它会沿祖先链把「有嫌疑」的节点全打印出来:
let el = document.querySelector('.sidebar');
while (el && el !== document.documentElement) {
const s = getComputedStyle(el);
if (s.overflow !== 'visible') console.log('裁剪/滚动祖先:', el, s.overflow);
if (s.transform !== 'none') console.log('包含块:', el, s.transform);
el = el.parentElement;
}
另外在 DevTools 的 Elements 面板选中元素,看 Computed 里 position 是否真的是 sticky(如果被其他规则覆盖会显示实际值),再临时加 outline: 2px solid red 观察它的活动范围。
兼容性方面,现代 Chrome / Edge / Firefox / Safari 都支持;只有 IE 全系不支持,老版本 iOS Safari 需要写 -webkit-sticky。如果你的用户里有 IE,就只能用 fixed + JS 监听滚动降级。
小结
position: sticky 必须有方向阈值(top 等),否则等同 relative。
- 它相对「最近的滚动祖先」生效,不是相对视口。
- 三大高频失效原因:祖先
overflow 不为 visible、Flex 子项被拉伸、祖先有 transform。
- 一定要给粘性元素加不透明背景和合适的
z-index。
- 相比
fixed,sticky 保留文档占位、受父容器约束,适合表头、侧栏、段落标签这类「区域内吸附」需求。