Vue3+TypeScript实现锚点组件

Vue 3 锚点组件实现

在这篇文章中,我们将介绍如何使用 Vue 3 和一些现代 Web 技术实现一个锚点导航组件。该组件用于在页面中展示文章目录,用户可以点击目录项快速跳转到页面中的相应部分,并且在用户滚动页面时,高亮显示当前所在的章节。

组件结构

本锚点组件包含两个主要部分:

  1. 侧边栏:用于显示文章的目录列表。每个目录项都可以是一个独立的链接,或者包含子项。
  2. 滚动监听:在用户滚动页面时,根据页面滚动位置动态更新高亮显示的目录项。

下面将详细讲解组件的实现。


组件模板

<template>
  <view class="container">
    <section ref="fixedContainer" class="blog-side-anchor fixed" :style="{ top: topDistance }">
      <div>
        <ul class="anchor">
          <div v-if="items.length" class="line" :style="{ height: lineHeight + 'px' }">
            <div
              v-show="moveLineTop !== 0"
              class="move-line"
              :style="{ top: moveLineTop + 'px' }"
            ></div>
          </div>
          <div>
            <li v-for="(v, i) in items" :key="i" class="li">
              <a
                class="anchor-link first-level"
                :class="{ active: activeLink === v.href }"
                :href="'#' + v.href"
                @click="onAnchorClick(v)"
              >
                <span>{{ v.title }}</span>
              </a>
              <!-- 渲染子项 -->
              <ul v-if="v.children?.length" class="collapse li">
                <li v-for="(n, k) in v.children" :key="k">
                  <a
                    class="anchor-link"
                    :class="{ active: activeLink === n.href }"
                    :href="'#' + n.href"
                    style="width: 224px"
                    @click="onAnchorClick(v)"
                  >
                    {{ n.title }}
                  </a>
                </li>
              </ul>
            </li>
          </div>
        </ul>
        <div class="custom"></div>
      </div>
    </section>
  </view>
</template>

关键部分:

  1. 锚点列表 (<ul class="anchor">)

    • 该部分渲染了文章目录,通过 v-for 循环遍历 items 数组。
    • 每个目录项 (<a>) 都是一个链接,点击时触发 onAnchorClick 方法。
  2. 滚动条和移动线 (<div class="line">)

    • 在目录列表上方有一条纵向线条,表示滚动位置。这个移动线会根据页面滚动更新位置,以指示当前所在的章节。

组件逻辑

组件的核心逻辑实现了以下几个功能:

1. 监听滚动事件

组件通过监听容器的滚动事件来动态更新当前的活动目录项。

const handleScroll = () => {
  flag.value = true;
  const currentPosition = scrollContainer.value ? scrollContainer.value.scrollTop : 0;
  for (let i = offsetList.value.length - 1; i >= 0; i--) {
    const section = offsetList.value[i];
    if (currentPosition >= section.offsetTop!) {
      activeLink.value = section.href;
      updateMoveLinePosition(section.href);
      break;
    }
  }
  lastPosition.value = currentPosition;
  flag.value = false;
};
  • 每次滚动时,handleScroll 会检查当前滚动位置 currentPosition,然后遍历 offsetList 获取每个锚点项的位置,更新 activeLink 和移动线的位置。

2. 获取锚点列表及其位置

const getOffsetList = () => {
  let list: AnchorItemProps[] = [];
  props.items.forEach(item => {
    if (item.children) {
      list.push(...item.children);
    }
    list.push(item);
  });

  // 重新计算每个目录项的 offsetTop
  offsetList.value = list
    .map(item => {
      let targetElement = document.getElementById(item.href) as HTMLElement;
      let top = Math.round(targetElement?.offsetTop) - 10;
      return {
        title: item.title,
        href: item.href,
        offsetTop: top,
      };
    })
    .sort((a, b) => a.offsetTop - b.offsetTop); // 按照 offsetTop 排序
};
  • getOffsetList 方法计算每个目录项对应的页面位置(offsetTop),并将其保存在 offsetList 数组中。此数组将用于滚动时匹配当前的锚点。

3. 更新移动线位置

const updateMoveLinePosition = (href: string) => {
  let aList = document.querySelectorAll('.anchor .anchor-link');
  for (let i = 0; i < aList.length; i++) {
    if (aList[i].getAttribute('href') === `#${href}`) {
      moveLineTop.value = aList[i].offsetTop;
      break;
    }
  }
};
  • updateMoveLinePosition 根据当前活动的 href 更新移动线的位置,确保用户能够看到当前所在的章节。

样式

在样式部分,我们使用了 less 来处理页面布局和样式。主要的样式包括:

  • 侧边栏布局:固定定位,右侧显示,宽度 240px,最大高度 600px。
  • 目录列表:通过 flex 布局将目录项垂直排列,并隐藏滚动条。
  • 高亮显示:当前活动的目录项会使用 .active 类进行高亮。
.fixed {
  position: fixed;
  top: 150px;
  right: 0;
  z-index: 1000;
  width: 240px;
  display: flex;
  justify-content: flex-start; /* 垂直排列内容 */
}

.anchor {
  border-radius: 4px;
  padding: 21px 24px;
  width: 204px;
  max-height: 600px;
  overflow-y: scroll;
  position: relative; /* 添加相对定位 */
  display: flex;
  flex-direction: column; /* 使子项竖着排列 */
}

.line {
  position: absolute;
  top: 0;
  left: 10px;
  width: 2px;
  height: calc(100% - 21px);
  background-color: #ededed;
  z-index: 99;
  .move-line {
    position: absolute;
    width: 2px;
    height: 16px;
    background-color: #7367f0;
    z-index: 100;
    transition: top 0.3s ease; /* 添加过渡动画 */
  }
}

.active {
  color: #7367f0 !important;
}

.anchor::-webkit-scrollbar {
  display: none;
}

适配响应式

为了适配不同设备的屏幕尺寸,当屏幕宽度小于 1024px 时,隐藏锚点导航栏:

@media (max-width: 1024px) {
  .anchor {
    display: none;
  }
}

总结

这个 Vue 3 锚点导航组件提供了一种便捷的方式来实现页面内的快速导航。它能够在页面滚动时动态高亮显示当前所在章节,并且支持嵌套的子目录项。组件的核心包括:

  • 监听滚动事件并根据滚动位置更新锚点高亮。
  • 使用 offsetTop 获取锚点的相对位置。
  • 使用 CSS 和 Vue 动态更新样式,确保用户体验。

通过这个组件,用户可以在长篇文章中轻松地跳转到指定章节,提高了页面的可用性和导航体验。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值