阅读 4126

CSS Sticky 其实很简单

为什么要写这篇文章

Sticky 也不是新知识点了,写这篇文章的原因是由于最近在实现效果的过程中,发现我对 Sticky 的理解有偏差,代码执行结果不如预期。决定写篇文章重新学习一次。

什么是 Sticky

Sticky (MDN 翻译成粘性效果)是 CSS 属性 position 中的一个可选值。跟我们用得比较多的 static, fixedrelativeabsolute 一样,用来描述元素的定位方式。

从效果上看,Sticky 像是混合体,页面滑动到“临界点”之前表现为 relative, 到达“临界点”时表现为 fixed

如何使用

使用 CSS Sticky 只需要两个条件。

position: sticky;
top: 0; // right/bottom/left 任一有效值,甚至可以为负像素值
复制代码

top:0 意思是当元素滑动到距离视口 0px 时再继续滑动,元素吸顶。可以在 这里 看效果(试试看修改 top 值)

对比 JS 的实现方案

没有 CSS Sticky 之前,类似的效果都是使用 JS 实现。大致步骤如下:

  1. 监听滚动事件,计算目标元素距离视口的距离。
  2. 距离不满足条件时,按兵不动。
  3. 距离满足条件时,创建占位元素,修改目标元素定位方式为 fixed
window.addEventListener('scroll', () => {
    const rect = elem.getBoundingClientRect();
    // 计算目标元素和视口的距离
})
复制代码

在 npm 上搜 sticky 关键字,也有很多优秀的包可以使用。以 react-sticky 为例,满足条件时会创建 placeholder 元素(防止页面抖动),同时让 header 定位为 fixedReact Sticky

右边是 Chrome Dev-Toolslayers 面板,蓝色部分为生成的 placeholder

两种方案的火焰图对比(为了放大效果,我把 cpu 调慢了 6 倍)

CSS 方案

css 方案

使用 CSS Sticky,工作都交给 GPU 了,不占用 JS 主线程的资源,在移动端上异常流畅。

React Sticky

React Sticky 方案

由于需要在 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

caniuse sticky

相关链接

文章分类
前端
文章标签