开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第10天,点击查看活动详情
锚点导航在现实中经常遇到,最常见就是文档之类的网站,比如
vue
官网、react
官网、antd
官方文档等。今天就写一个锚点导航供需要的人参考,本文是基于vue + element-ui
。
💥基础功能
除了页面布局外,只需要几行代码就能实现一个简单的导航,通过scrollIntoView()
,此方法会滚动元素的父容器,使被调用 scrollIntoView() 的元素对用户可见。效果如下:
语法(更详细的介绍可以参考👉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>
🔥进阶功能 👉在线运行
现在基础功能已经实现了,处理点击能到指定位置,我们也需要鼠标滚动的时候,锚点也跟着联动,这个有点麻烦,需要监听鼠标滚动事件,先看效果图:
完整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>