先记住这个答案
sticky 元素开始时和 relative 一样在文档流里正常排列。当页面滚动到它离滚动容器可视区顶部的距离等于 top 的值时,它就停在那里不动了,这就是粘住。继续往下滚,一旦它的父元素底边快要滚出去,父元素会把它一起带走,因为 sticky 元素不能离开自己的父元素。要生效必须满足三点:设了 top 等阈值;最近的滚动容器确实在滚;父元素比它高,留有移动空间。
- 必须设置
top、bottom等至少一个阈值 - 滚到阈值位置开始粘,参照是最近的滚动容器
- 粘性范围不会超出父元素,父元素走它也走
- 没粘住时和
relative一样占着原位置
典型实现:吸顶导航
最常见的需求是导航栏滚到顶部后固定住,页面内容从它下面滚过去。
<style>
.nav {
position: sticky;
top: 0; /* 距离视口顶部 0 时开始粘住 */
z-index: 10; /* sticky 会创建层叠上下文,给个层级防止被内容盖住 */
background: #fff; /* 不设背景,下面的内容会透出来 */
}
</style>
<body>
<div class="banner">顶部横幅,高 200px</div>
<nav class="nav">导航栏</nav>
<main>很长的正文……</main>
</body>
<!-- 先滚 200px,横幅滚走;导航到达顶部后停住,一直停到页面底部 -->这里导航的父元素是 body,高度就是整个页面,所以它能一直粘到底。和 position: fixed 相比,sticky 不脱离文档流,不用给正文加 padding-top 来补位,横幅滚走之前导航也在它该在的位置。
什么时候被带走:父元素边界
sticky 最容易被忽略的一条规则是:它的活动范围被限制在父元素里。粘住之后,如果继续滚动让父元素的底边接近它,它就会被推着一起往上走。
- 通讯录分组标题
- 通讯录里 A、B、C 每组各有一个标题,每组是一个
section,标题设position: sticky和top: 0。滚动时 A 的标题先粘在顶部;A 组快滚完时,A 组的section底边把 A 标题往上推走,紧接着 B 的标题顶上来。这个「一个顶走一个」的效果完全是父元素边界带来的,不需要写任何 JavaScript。 反过来,这条规则也是吸顶失效的常见原因。如果把导航包在<header>里,而header的高度正好等于导航,导航就没有任何移动空间,看起来就像完全没生效。
阈值和滚动容器怎么算
阈值不只是 top,还有 bottom、left、right。比如 bottom: 0 可以做底部吸附的操作栏:元素还没滚进视口时就先贴在底部,滚到它原本的位置后恢复正常。
阈值计算的参照是最近的滚动容器,也就是往上找到的第一个 overflow 为 hidden、auto、scroll 的祖先。在页面上它就是视口;如果放在一个 overflow: auto 的侧边栏里,就是相对侧边栏的可视区。如果中间有一层 overflow: hidden 又不滚动,sticky 就会参照这个不动的容器,永远触发不了。
容易答错的地方
- 只写了
position: sticky,没写阈值 - 没有
top、bottom这些阈值,sticky的表现和relative完全一样,不会粘住。top: 0这一行是必须的。 - 以为
sticky能粘满整个页面 sticky只能在父元素范围内粘。想要全页吸顶,父元素要足够高,最好直接是body或页面主容器。- 在 flex 容器里被拉伸
- flex 子项默认
align-items: stretch,侧边栏会被拉得和父元素一样高,没有移动空间,sticky失效。给这个子项加align-self: flex-start就好。
面试官还会怎么问?
sticky 和 fixed 怎么选?
fixed 脱离文档流,相对视口固定,始终不动,需要自己给下方内容留位置。sticky 在文档流里,先跟着滚动,到阈值才固定,而且受父元素限制。吸顶导航、分组标题、表头用 sticky;全局悬浮按钮、固定底栏用 fixed。
怎么知道 sticky 元素当前是否处于粘住状态,比如粘住后加阴影?
Chromium 系浏览器新加了 scroll-state 容器查询,可以直接判断是否粘住,但还不是所有浏览器都支持。兼容性更稳的做法是在 sticky 元素上方放一个 1px 高的哨兵元素,用 IntersectionObserver 监听它,哨兵离开视口就说明已经粘住,给导航加一个 class。
同时写 top 和 bottom 会怎样?
两个阈值都会生效:元素向上滚时在距顶部 top 处粘住,向下时在距底部 bottom 处粘住。如果滚动容器太矮放不下,两者冲突时 top 优先。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。