需求描述

需要实现类似doc中文档大纲的效果,点击对应章节的名称时定位到相应的正文;而当正文滚动时,高亮显示对应的章节名称
章节联动效果图

实现思路

其实笔者一开始想到的是利用a标签页内跳转(也就是“锚点”),类似于Element-UI官方文档:单选框组
当鼠标悬浮到页内某一章节名称时,会显示符号为“¶”的锚点,点击锚点会在网页链接后拼接对应hash值实现页内跳转
饿了么UI官方文档-锚点实现页内跳转
但仅靠锚点难以实现滚动页面内容时,定位到对应章节名称。。还是得靠度娘
经过了一番搜索与尝试,我发现所有标注了“vue锚点双向滚动”的文章实际都采用了监听滚动/滚动页面的实现思路和“锚点”其实并无关联。。,总之,笔者最终翻到了这篇锚点双向定位博客,虽然原文的示例代码稍显粗糙,但这篇的本质其实就是经过个人润色的那篇博客哦☆♦☆!

示例代码

请在你本地引入了Element-UI的项目中,新建一个test.vue来运行以下示例代码~

<!--
 * @Author: smm
 * @Date: 2022-09-19 15:33:32
 * @LastEditors: smm
 * @LastEditTime: 2022-09-19 17:27:08
 * @Description: 章节联动滚动示例
-->
<template>
  <div class="sectionTest">
    <div>
      <el-radio-group
        v-model="sectionIdx"
        @change="change"
      >
        <el-radio
          v-for="(item, index) in sectionList"
          :key="item"
          :label="index"
        >
          {{ item }}
        </el-radio>
      </el-radio-group>
    </div>
    <div
      class="contentBlock"
      @scroll="scrollEvent"
    >
      <div
        v-for="item in sectionList"
        :key="item"
        class="content"
      >
        {{ `正文:${item}` }}
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: ``,
  data () {
    return {
      sectionList: [`第一章`, `第二章`, `第三章`, `第四章`, `第五章`], // 章节标题列表
      sectionIdx: 0 // 当前的章节下标
    }
  },
  methods: {
    change () {
      let contents = document.querySelectorAll('.content')
      contents[this.sectionIdx].scrollIntoView({ block: 'start', behavior: 'smooth' })
    },
    scrollEvent (e) {
      const { sectionList } = this
      let contents = document.querySelectorAll('.content')
      let nowTop = e.target.scrollTop // 滚动条目前的滚动距离
      let firstTop = contents[0].offsetTop // 第一个章节的顶部位置
      let idx = null
      try { // forEach循环只能通过抛出异常的方式终止
        sectionList.forEach((section, index) => {
          let contentTop = contents[index].offsetTop // 第index个章节的顶部位置
          let scrollDistance = contentTop - firstTop // 从第一个章节到第index个章节的滚动距离
          if (nowTop < scrollDistance) {
            // 滚动条已滚动的距离少于第index个章节需要的滚动距离,说明当前正文处于第(index-1)的章节
            idx = Math.max(0, index - 1)
            throw new Error(`find section:${this.sectionIdx}`)
          }
        })
      } catch (e) { }
      // 滚动到最后一章(该章节高度超过正文容器)时,滚动条的滚动距离超过所有章节需要的滚动距离
      this.sectionIdx = idx === null ? sectionList.length - 1 : idx
      if (e.srcElement.scrollTop + e.srcElement.offsetHeight === e.srcElement.scrollHeight) {
        // 滚动条触底,处于最后一章(消除该章节高度低于正文容器时始终定位在倒数第二章的情况)
        this.sectionIdx = sectionList.length - 1
      }
    }
  }
}
</script>

<style lang="scss">
.sectionTest {
  height: 500px;
  background: #fff;
  padding: 0 20px;
  .contentBlock{
    height: 400px;
    overflow-y: auto;
    &::-webkit-scrollbar {
      width: 10px;
      height: 10px;
    }
    &::-webkit-scrollbar-thumb {
      background:#ccc;
      border: 1px solid green;
      border-radius: 5px;
    }
    &::-webkit-scrollbar-track {
      background: #666;
      border-radius: 5px;
    }
  }
  .content {
    border: 1px solid red;
    margin: 10px 0;
    color: orangered;
    &:nth-child(even) {
      height: 500px;
      background: #aaa;
    }
    &:nth-child(odd) {
      height: 300px;
      background: #eee;
    }
  }
}
</style>

参考网址

[1] 锚点双向定位
[2] Element-UI单选框组
[3] Js中forEach跳出本次循环和终止循环

Logo

前往低代码交流专区

更多推荐