好了,阶段性的发布又来了。最近碰到一个需求,做一个类似于antd-archor的组件功能。
先看视图
好的,这交互就显而易见了吧,navtab的sticky,非常合理,不容置疑的合理。但是,单单是这个滚动区域,就已经是为难我了,按照项目页面结构的搭建,我的滚动区域实际上是这样的:
所有的内容都在面包屑之下的容器内滚动。并且,stikcy之后,这个tab的宽度明显突破外面的限制了。 第一步就让人头大了,需要更改完成的滚动容器,并且并且,在sticky的定位元素之外有scroll的元素层,高度必须向下传递,否则sticky失效。 改动完滚动元素之后,另一个问题出现了,突破100%的宽度,要怎么处理,答:tab外,sticky内再加一层absolute容器,由它来超出这100%,但是又必须是吸顶情况下是加超出的宽度,终于还是要用到不想用的监听了,addEventListner监听tab的boundClientRect,来更改absolute容器的宽度样式。 还没完成,还有一个滚动自动切换锚点的功能,也是本次的主要内容。不想再用eventLisner的情况下,或者不想做一堆计算的情况下,笔者使用window下的IntersectionObserver,一个监听视图展示的工具,不多说,直接封装起来,上代码
/**
* IntersectionObserver
* @param {*} options 观测对象的必备参数
* @param {*} callback 回调处理函数
* @param {*} target 被观测对象的selector
* @param {*} scope 被观测对象组件内的父容器
*/
export default function intersectionObserver(
{
root = '', // isRequired
rootMargin = '0px',
observeAll = false,
thresholds = [1],
isRelativeTo = false,
restrictToIntersecting = true,
},
target,
callback,
) {
// 对H5 root 参数必传,无论是否基于viewport, 均需要 root 来协助查询被观测对象
// 若 root, 被观测 target 和 callback 参数不为 function, 均不执行后续观测
if (!root || !target || typeof callback !== 'function') return null;
// 查询root对应的parent节点,若查询不到,不执行后续观测
const parent = document.querySelector(root);
if (!parent || !IntersectionObserver) return null;
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((res) => {
if (restrictToIntersecting && !res.isIntersecting) return;
callback({
boundingClientRect: res.boundingClientRect,
intersectionRatio: res.intersectionRatio,
intersectionRect: res.intersectionRect,
relativeRect: res.rootBounds,
time: res.time,
id: res.target.getAttribute('id'),
});
});
},
{
root: isRelativeTo ? parent : null,
// 如果相对于viewport, root传null即可
rootMargin,
threshold: thresholds,
},
);
if (observeAll) {
const children = Array.from(parent.querySelectorAll(target)); // 待观测子节点为空,不继续观测
if (children.length <= 0) return null;
children.forEach((element) => {
observer.observe(element);
});
} else {
const child = parent.querySelector < Element > target; // 待观测节点不存在,不继续观测
if (!child) return null;
observer.observe(child);
}
return observer;
}
然后是使用
let observer = intersectionObserver(
{
root: '#tab-content',
observeAll: true,
thresholds: [0.9],
isRelativeTo: false
},
'.nav-tab-item',
debounce(({ id }) => {
// 这个id就是返回的锚点
}, 10),
);
好的,滚动检测就完成了,接下来的点击滚动到制定锚点就比较简单,直接使用scrollToView就完事了。 至此,这个看似非常合理的交互完成了