天天看点

CSS Sticky 其实很简单

为什么要写这篇文章

​Sticky​

​​ 也不是新知识点了,写这篇文章的原因是由于最近在实现效果的过程中,发现我对 ​

​Sticky​

​ 的理解有偏差,代码执行结果不如预期。决定写篇文章重新学习一次。

什么是 Sticky

​Sticky​

​​ (MDN 翻译成粘性效果)是 ​

​CSS​

​​ 属性 ​

​position​

​​ 中的一个可选值。跟我们用得比较多的 ​

​static​

​​, ​

​fixed​

​​,​

​relative​

​​,​

​absolute​

​ 一样,用来描述元素的定位方式。

从效果上看,​

​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​

​​ 定位为 ​

​fixed​

​。

右边是 ​

​Chrome Dev-Tools​

​​ 的 ​

​layers​

​​ 面板,蓝色部分为生成的 ​

​placeholder​

​。

两种方案的火焰图对比(为了放大效果,我把 ​

​cpu​

​ 调慢了 6 倍)

CSS 方案

使用 ​

​CSS Sticky​

​​,工作都交给 ​

​GPU​

​​ 了,不占用 ​

​JS​

​ 主线程的资源,在移动端上异常流畅。

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​

​ 。

相关链接

  • ​​uxdesign.cc/position-st…​​
  • ​​codyhouse.co/ds/componen…​​
  • ​​medium.com/@elad/css-p…​​
  • ​​developer.mozilla.org/zh-CN/docs/…​​
  • ​​stackoverflow.com/questions/4…​​