1、CSS Sticky实现
Sticky (MDN 翻译成粘性效果)是 CSS 属性 position 中的一个可选值。跟我们用得比较多的 static, fixed,relative,absolute 一样,用来描述元素的定位方式。
从效果上看,Sticky 像是混合体,页面滑动到“临界点”之前表现为 relative, 到达“临界点”时表现为 fixed。
如何使用sticky:
position: sticky;
top: 0; // right/bottom/left 任一有效值,甚至可以为负像素值
没有 CSS Sticky 之前,类似的效果都是使用 JS 实现。大致步骤如下:
- 监听滚动事件,计算目标元素距离视口的距离。
- 距离不满足条件时,按兵不动。
- 距离满足条件时,创建占位元素,修改目标元素定位方式为 fixed。
window.addEventListener('scroll', () => {
const rect = elem.getBoundingClientRect();
// 计算目标元素和视口的距离
})
在 npm 上搜 sticky 关键字,也有很多优秀的包可以使用。以 react-sticky 为例,满足条件时会创建 placeholder 元素(防止页面抖动),同时让 header 定位为 fixed。
性能比较
使用 CSS Sticky,工作都交给 GPU 了,不占用 JS 主线程的资源,在移动端上异常流畅。
由于需要在 scroll event 回调中不断调用 getBoundingClientRect,而 getBoundingClientRect 又会触发页面重排重绘,稍不留神就掉帧卡顿。仅仅为了实现这个效果(页面上没有其他内容)大动干戈性价比很低。
结论是:实现 Sticky 效果,优先选择 CSS Sticky
偏差
1. 只在 Containing Block 内有效。
修改例子,用一个 div 把 Sticky Header 包裹起来,发现 Sticky 效果失效了
...
<div class="wrapper">
<header>Sticky Header</header>
</div>
...
根据文档,Sticky 效果只在 Containing Block 内有效,Containing Block 滑出屏幕时,Stickey Element 也跟着滑走。
修改 wrapper 的高度,看效果。
.wrapper {
height: 100px;
background-color: #e6e6e6;
}
多个 Sticky Element 放在一块就有了前一个被后一个顶出去的特效,实际上并不是真的被顶出去,而是 Containing Block 把它拖走。
2、Overflow 会影响 Sticky
修改例子中的代码,给 #root 加上 overflow: auto
#root {
overflow: auto;
}
Sticky 效果再次丢失(overflow 设置为其他非 visible 的有效值也是同样效果。)
看了很多相关的文档,我的出来的结论是:
Sticky Element 的 offset 值是依据 nearest scrolling ancestor (距离最近的滚动祖先) 计算的,如果没有匹配上的祖先元素,则使用视口作为参照物。
问题就出在 overflow-x 或者 overflow-y 其中任一为非 visible 则认为是要找的目标元素,而在滚动窗口的过程中,Sticky Element 和 它找到的目标祖先元素的 offset 值一直没有改变,所以 Sticky 不起作用。
对症下药,让滚动发生在被“误匹配”上的祖先元素内即可恢复 Sticky Effect。
#root {
overflow: auto;
height: 100vh;
}
兼容性
算上 prefixed ,当前 css sticky 手机端兼容性达到 94.14%,如果你所做的业务需要照顾剩下 5.86% 的用户,也可以使用 polyfill 或者 position: fixed 。
2、使用原生的offsetTop实现
我们知道 offsetTop 是相对定位父级的偏移量,倘若需要滚动吸顶的元素出现定位父级元素,那么 offsetTop 获取的就不是元素距离页面顶部的距离。
3、使用 obj.getBoundingClientRect().top 实现
...