判断粘性定位元素(position: sticky)是否已经固定

1,394 阅读3分钟

粘性定位可以很方便的实现元素的贴边效果,但是如果想要在元素到达固定位置后改变其样式该怎么做?

最终效果:

chrome-capture-2023-6-3 (3).gif

观察元素与视窗的交叉

这里使用IntersectionObserverAPI

IntersctionObserver是一个用来观察目标元素和root交叉状态的API。

交叉是指目标元素和root的重叠部分。

root是其祖先中离其最近的滚动元素,我们先认为就是视窗。

回调函数的参数对象中有一个intersectionRatio,指的是交叉的百分比,当元素完全离开root就是0,完全在root中就是1,一半在root中就是0.5,以此类推。

Intersection Observer 完全解读

如果你尝试去搜索这个问题的答案,你大概能找到这个设置top: -1px的教程: How to Detect When a Sticky Element Gets Pinned, 你可以把里面的例子复制到自己的浏览器中试一试,我测试发现,这在Edge中并不好用,在Chrome和Firefox中正常。

向Edge提交了该问题,不过还没有收到处理完成反馈

chrome-capture-2023-6-3.gif

Edge 114.0.1823.67 (64 位)中运行效果

这种方式的原理很简单,通过设置top:-1px, 当元素往上滚动时,将会在停止在屏幕外面1px处,那么此时 intersectionRaio就不是1了, 我们就知道元素已经fixed。

temp1.png

由于Edge中元素一但开始滚动,IntersectionRatio就变成了0.99所以无法探测sticky是否已经固定。

同时我们希望元素可以定位在任意位置,比如在紧贴header而不是窗口,我们需要借助instersectionObserver的rootMargin参数。

IntersctionObserver的rootMargin参数

rootMargin可以有效的缩小或扩大root的判定范围从而满足计算需要

也就是说,我们可以给margin设置负值来缩小root判断的区域范围,如果我们要将元素粘到top:48px处,那我们可以将rootMargin设置为-48px 0px 0px 0px

temp1.png

这时候就可以给粘性元素设置top: 47px来实现任意位置的粘性定位判断了。

接下来解决Edge中的问题,通过设置threshold: [0, .1, .2, .3, .4, .5, .6, .7, .8, .9, 1]参数, 观察instersectionRatio的值,可以发现,当元素全部在root中时,instersectionRatio有时是0.99并不是1,当元素完全不在root中时instersectionRatio是0,总结发现0的情况更稳定。所以我们可以把判断从instersectionRatio < 1转换成instersectionRatio === 0

我们可以把root区域再缩小些,使得当元素fixed时刚好超出root判定区域,instersectionRatio为0。

那么rootMargin的值就是-${粘性元素高度 + 距离视窗顶部高度 + 1}px

在我们上面的例子里就是-${64 + 48 + 1}px => -113px

temp1.png

这种方式在Edge/Chrome/Firefox中均稳定。建议把代码粘贴到自己的浏览器中实机运行

最后

同样的,也可以实现其他方向的滚动判断。

当fixed后不要改变粘性元素尺寸位置相关的css,否则页面将无限抖动。