实现目录滚动高亮|青训营笔记

178 阅读2分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 14 天

目录点击时样式

  • 点击对应标题时,通过点击事件传递当前的标题index,通过定义一个 activeIndex 来判断是哪个导航条高亮样式。
  • 这里通过获取的h标签等级来进行导航条的缩进 :style="{ marginLeft: `${hItem.id * 10}px` }
          <li v-for="(hItem, index) in hList" :key="index" :title="hItem.content"
            :class="[hItem.id * 1 === 1 ? 'a-container-h1' : 'a-container-h2', activeIndex === index ? 'active' : '']">
            <div class="a-container" :style="{ marginLeft: `${hItem.id * 10}px` }">
              <a :href="hItem.jumpId" @click="jump(index)">{{ hItem.content }}</a>
            </div>
          </li>
let activeIndex = ref(0);
const jump = (index) => {
  activeIndex.value = index;
};

导航高亮样式:

.active {
  a {
    color: var(--theme-dir-active);
  }

  & ::before {
    content: '';
    position: absolute;
    top: 2px;
    left: 0;
    margin-top: 7px;
    width: 4px;
    height: 16px;
    background: var(--theme-dir-active);
    border-radius: 0 4px 4px 0;
  }
}

目录滚动高亮优化

在页面挂载完成后,获取所有需要的h标签距离顶部的距离存入 hTopList 数组。

bug发现!

如果文章中存在图片,图片还未加载完就获得了hTopList,导致目录点击转跳跟随错位,我明明使用的a标签转跳,img的src加载...。虽然我添加了延时,本地是解决了但是放到服务器上不行,所以本项目中没让数据人员给我在文章中添加图片数据。

我还尝试在文章图片加载完后,再获取hTopList,但也失败了,我觉得这和markdown css里定义的img样式有关。我应该去解决一下。

//获取所有h标签的offsetTop
let hTopList = ref([]);
const getHTopList = () => {
  let arr = [];
  for (let i = 0; i < hList.length; i++) {
    arr.push(document.querySelector(hList[i].jumpId).offsetTop);
  }
  arr.push(Number.MAX_VALUE); //兜底
  hTopList.value = arr;
};

目录跟随文章滚动

监听页面滚动,对比页面滚动值scrollTop和标签的高度来判断是否高亮,

  for (let i = 0; i < hTopList.value.length; i++) {
    if (scrollTop.value > hTopList.value[i] && scrollTop.value < hTopList.value[i + 1] && activeIndex.value != i) {
      activeIndex.value = i;
    }
  }

滚动时样式优化

这里通过滚动目录div来实现。这里的keep参数就是导航条过多时,滚动条高亮处在第七个,其实应该根据导航box的高度对半取整算一下,这里图方便,数了下掘金是在第几个高亮就写了第几个。34 是每条导航 li 标签的高度。

  let catalogList = document.querySelector('.catalog-list');
  const keep = 7; 
  let hListLength = hList.length;
  if (activeIndex.value <= keep) {
    catalogList.scrollTop = 0;
  } else if (activeIndex.value > hListLength - keep) {
    catalogList.scrollTop = 34 * (hListLength - keep);
  } else {
    catalogList.scrollTop = 34 * (activeIndex.value - keep);
  }

函数节流

在实现文章目录滚动样式,使用到了 window.addEventListener 监听鼠标滚动。每一次滚动都会调用一次内部函数。这里采用节流,每0.1秒(草)内行数只执行一次。

let boxTop = ref(0);
let scrollTop = ref(0);
let scroll;
let timer;
onMounted(() => {
  setTimeout(() => {
    boxTop.value = document.querySelector('.catalog-box').offsetTop;
  }, 0);
  setTimeout(() => {
    getHTopList();
  }, 2000);
  getHTopList();
  window.addEventListener(
    'scroll',
    (scroll = () => {
      if (timer) {
        return;
      }
      timer = setTimeout(() => {
        scrollTop.value = window.scrollY || window.pageYOffset || document.documentElement.scrollTop;
        console.log(scrollTop.value);
        handleScroll();
        watchActive();
        timer = null;
      }, 100);
    }),
  );
});