让我们先看看谷歌demo的效果
这是开发者中心的网址,想自己深入研究的童鞋可以去看看(全英) developer.chrome.com/blog/sticky…
相信大家在平时使用的app里都见过这种效果,导航栏在顶部时正常状态,下滑时吸顶并出现阴影或者其他特效,如果你使用element-plus组件,可以使用affix固钉组件,里面提供了相应的判断正常状态还是sticky状态的api,但为了有更多操作的余地,我们也可以自己手动实现一个,就从上面的demo开始分析。
(码上掘金事例请打开详情查看,iframe太小效果不好)
这是上面的demo的源码,相信很多人一看这么长就不想看;但其实,里面有很多代码都是为了循环生成多个事例而编写的,大大增加了分析的难度:
红框中是一篇文章,往下滑时上方sticky的蓝色div就会出现阴影,滑到第二篇文章时上一篇文章的sticky就会恢复原状,而滑到第二遍这部分不是我们今天要讨论的主题,我们要探讨的是向下滑时sticky出现阴影,回到顶部时阴影消失;并且我们也不需要这么多个文章来体现多个sticky,我们只需要保留一个文章来体现效果就可以了。
于是我就把代码中没有必要的部分全部剔除了,代码如下: (剔除了多个文章片段,只保留一个能够体现效果;剔除了手动生成的dom,直接写在html中;剔除了监听是否到达下一篇文章使sticky恢复的代码)
剔除过后的代码是不是清晰了很多?html结构如下:
红框为contanier,父级容器,后面出现父级容器就是代表它;蓝框为包裹所有文字的容器,滚动条就是因为该容器高度超过父级容器的高度而产生的;黄框为sticky的元素;而最关键的就是灰色框里的sticky_sentinel元素了。让我们动脑子想一想,如何监听sticky元素是否处在fixed状态?(sticky的原理就是页面滑动到“临界点”之前表现为 relative
, 到达“临界点”或之后表现为 fixed
)intersectionObserver可以监听元素进入视口或离开视口或到达视口某一阈值的情况并执行传入的回调函数,但是sticky的元素却一直在视口范围内,我们应该如何去实现呢?
答案就是监听一个替代元素,也就是上面说到的sticky_sentinel元素了(这个名字是谷歌开发者demo里取的,实际上叫啥都一样)。分析结构会发现初始状态替身的位置跟sticky元素的位置是一样的,但是替身的position设置的是absolute,因此它会一直保持在原来的位置,那我们是不是可以监听替身是否出现在视口,从而判断sticky是否处在fixed状态呢。知道原理之后,再看看上面的代码,就清晰很多了
function observeHeaders(container) {
const observer = new IntersectionObserver((records, observer) => {
for (const record of records) {
console.log(record);
const targetInfo = record.boundingClientRect;
const stickyTarget = record.target.parentElement.querySelector('.sticky');
const rootBoundsInfo = record.rootBounds;
if (targetInfo.bottom < rootBoundsInfo.top) {
stickyTarget.classList.toggle('shadow', true)
}
if (targetInfo.bottom >= rootBoundsInfo.top &&
targetInfo.bottom < rootBoundsInfo.bottom) {
stickyTarget.classList.toggle('shadow', false)
}
}
}, {
// rootMargin: '-16px',
threshold: [0],
root: container
});
const sentinels = container.querySelector('.sticky_sentinel')
observer.observe(sentinels)
}
核心函数就是observerHeaders,传入一个元素,该元素将成为监听元素的父级元素(上面提到过),然后创建一个IntersectionObserver,获取boundingClientRect和rootBounds属性,并获取真正sticky的元素(这里使用record.target.parentElement.querySelector('.sticky')是因为原demo有多个文章,需要获取文章对应的sticky,只有一个的话直接根据类名获取就好了),根据boundingClientRect和rootBounds的关系对sticky的样式进行操作即可(原demo将添加样式这一操作写成documnet的一个事件,并在监听时触发该事件,跟直接操作基本上是一样的)。
最后从零写一个属于自己的demo 阴影触发位置可以通过调整替身的高度进行调整。这样原来谷歌开发者中心400多行的demo就被我们缩略成不到一百行了!相信看了最后的代码你也能够自己写出属于自己的吸顶阴影demo了。
写文不易,若文中有任何错误或需要补充的,请在评论区提醒,将会虚心请教