vue锚点导航

1,569 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第10天,点击查看活动详情

锚点导航在现实中经常遇到,最常见就是文档之类的网站,比如vue官网、react官网、antd官方文档等。今天就写一个锚点导航供需要的人参考,本文是基于vue + element-ui

💥基础功能

除了页面布局外,只需要几行代码就能实现一个简单的导航,通过scrollIntoView(),此方法会滚动元素的父容器,使被调用 scrollIntoView() 的元素对用户可见。效果如下:

QQ录屏20221214194954.gif

语法(更详细的介绍可以参考👉MDN Web Docs)

element.scrollIntoView(); // 等同于 element.scrollIntoView(true) element.scrollIntoView(alignToTop); // Boolean 型参数 element.scrollIntoView(scrollIntoViewOptions); // Object 型参数

参数

alignToTop(可选):

如果为true,元素的顶端将和其所在滚动区的可视区域的顶端对齐。相应的scrollIntoViewOptions: {block: "start", inline: "nearest"}。这是这个参数的默认值。 如果为false,元素的底端将和其所在滚动区的可视区域的底端对齐。相应的scrollIntoViewOptions: {block: "end", inline: "nearest"}。 scrollIntoViewOptions(可选)一个包含下列属性的对象:

behavior 可选 定义动画过渡效果, "auto"或 "smooth" 之一。默认为 "auto"。

block 可选 定义垂直方向的对齐, "start", "center", "end", 或 "nearest"之一。默认为 "start"。

inline 可选 定义水平方向的对齐, "start", "center", "end", 或 "nearest"之一。默认为 "nearest"。

<template>
  <el-row :gutter="20">
    <el-col :span="18">
      <div class="box-col" id="box">
        <el-card :id="item.id" class="box-card" v-for="item in navList" :key="item.value">
          <div slot="header" class="clearfix">
            <span>{{item.label}}</span>
          </div>
          <div v-for="o in 4" :key="o" class="text item">
            {{'列表内容 ' + o }}
          </div>
        </el-card>
      </div>
    </el-col>
    <el-col :span="6">
      <div class="navBox">
        <div @click="goAnchor(item.id, item.value)" :class="['navItem', navActive == item.value ? 'active' : '']" v-for="item in navList" :key="item.value">
          {{ item.label }}
        </div>
      </div>
    </el-col>
  </el-row>
</template>
<script>
export default {
  name: "Anchor",
  data() {
    return {
      navActive: 1,
      navList: [
        {
          label: "卡片名称1",
          value: 1,
          id: "part1",
        },
        {
          label: "卡片名称2",
          value: 2,
          id: "part2",
        },
        {
          label: "卡片名称3",
          value: 3,
          id: "part3",
        },
        {
          label: "卡片名称4",
          value: 4,
          id: "part4",
        },
        {
          label: "卡片名称5",
          value: 5,
          id: "part5",
        },
      ],
    };
  },
  methods: {
    // 锚点导航-主要代码逻辑
    goAnchor(keyId, val) {
      this.navActive = val;
      document.getElementById(keyId).scrollIntoView(true);
    },
  },
};
</script>
<style scoped>
html {
  overflow: hidden;
}
.box-col {
  height: 90vh;
  overflow-y: auto;
  box-sizing: border-box;
  scroll-behavior: smooth;
}
.box-card {
  height: 500px;
  margin-bottom: 10px;
}
.navBox {
  margin-left: 10px;
  border-left: 2px solid rgba(0, 0, 0, 0.09);
}
.navItem {
  font-family: PingFangSC-Regular, PingFang SC;
  font-weight: 400;
  color: #000000;
  padding: 10px;
  margin-left: -2px;
  cursor: pointer;
  border-left: 2px solid transparent;
}
.navItem:hover {
  color: #177be6;
}
.navItem.active {
  color: #177be6;
  border-left: 2px solid #177be6;
}
</style>

🔥进阶功能 👉在线运行

现在基础功能已经实现了,处理点击能到指定位置,我们也需要鼠标滚动的时候,锚点也跟着联动,这个有点麻烦,需要监听鼠标滚动事件,先看效果图:

2222.gif

完整js代码如下:

需要注意的一点是,navClickFlag标识,因为,点击的时候scrollIntoView(true)会触发滚动,这个时候执行滚动函数,如果不加的话,锚点active状态会出现抖动,所以添加标识,点击锚点的时候尽量避免滚动函数里的逻辑。

<script>
export default {
  name: "Anchor",
  data() {
    return {
      //锚点点击标识, 不然active就会来回抖动
      navClickFlag: false,
      // 滚动元素id
      scrollWrap: "box",
      navActive: 1,
      navList: [
        {
          label: "卡片名称1",
          value: 1,
          id: "part1",
        },
        {
          label: "卡片名称2",
          value: 2,
          id: "part2",
        },
        {
          label: "卡片名称3",
          value: 3,
          id: "part3",
        },
        {
          label: "卡片名称4",
          value: 4,
          id: "part4",
        },
        {
          label: "卡片名称5",
          value: 5,
          id: "part5",
        },
      ],
      // 各锚点的高度
      offTopLs: [],
    };
  },
  mounted() {
    // 监听滚动
    window.addEventListener("scroll", this.handleScroll, true);
    this.$nextTick(() => {
      // 计算锚点的高度
      this.getoffTop();
    });
  },
  beforeDestroy() {
    // 销毁监听
    window.removeEventListener("scroll", this.handleScroll);
  },
  methods: {
    // 计算锚点的高度
    getoffTop() {
      let offTop = [];
      for (let index = 0; index < this.navList.length; index++) {
        const element = this.navList[index];
        if (document.getElementById(element.id)) {
          offTop.push(document.getElementById(element.id).offsetTop);
        }
      }
      this.offTopLs = offTop;
    },
    // 监听滚动
    handleScroll() {
      if (!this.navClickFlag && document.getElementById(this.scrollWrap)) {
        let scTop = document.getElementById(this.scrollWrap).scrollTop;
        for (let index = 0; index < this.offTopLs.length; index++) {
          const element = this.offTopLs[index];
          let offsetTop = document.getElementById(this.scrollWrap).offsetTop;
          if (scTop + offsetTop >= element) {
            this.navActive = index + 1;
          }
        }
      }
    },
    // 锚点导航
    goAnchor(keyId, val) {
      this.navClickFlag = true;
      this.navActive = val;
      document.getElementById(keyId).scrollIntoView(true);
      setTimeout(() => {
        this.navClickFlag = false;
      }, 2000);
    },
  },
};
</script>