IntersectionObserver的使用

257 阅读2分钟

好了,阶段性的发布又来了。最近碰到一个需求,做一个类似于antd-archor的组件功能。 先看视图 image.png

image.png

好的,这交互就显而易见了吧,navtab的sticky,非常合理,不容置疑的合理。但是,单单是这个滚动区域,就已经是为难我了,按照项目页面结构的搭建,我的滚动区域实际上是这样的: image.png

所有的内容都在面包屑之下的容器内滚动。并且,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就完事了。 至此,这个看似非常合理的交互完成了