手写vue3锚点组件
作者:互联网
1、父组件
<template>
// anchorList为锚点的列表 currentId为当前显示的id offsetTop相对位置的top高度
<div class="right">
<AnchorVue :anchorList="curAnchorList" :currentId="id" offsetTop="120"></AnchorVue>
</div>
</template>
<script lang='ts'>
export default {
components: { AnchorVue },
setup() {
const route: any = useRoute();
const state= reactive({
curAnchorList: [ // id为标签的id name为锚点展示的文案
{id: 'basicMess', name: '锚点A'},
{id: 'basicPlan', name: '锚点B'},
{id: 'basicWarn', name: '锚点C'},
],
id: 'basicMess'
})
return {
...toRefs(state)
}
}
}
<script>
<style lang="less">
.right {
width: 160px;
}
</style>
2、子组件AnchorVue
<template>
<div class="affix">
<div class="anchor">
<div class="anchor__basic">
<div class="anchor__basic-item" v-for="item in anchorList" :key="item.id" :class="{'active': item.id===id}" @click="local(item.id)">{{item.name}}</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, onActivated, onDeactivated, onMounted, PropType, reactive, toRefs, watch } from "vue";
interface ANCHORLIST { // 锚点列表
id: string;
name: string
}
interface STATE {
id: string | undefined, // 当前的id
anchorList: Array<ANCHORLIST> | undefined,
timer: number,
}
export default defineComponent({
props: {
anchorList: {
type:Array as PropType<ANCHORLIST[]>,
},
currentId: {
type: String
},
offsetTop: { // 距离当前顶部高低
type: String
}
},
setup(props,ctx) {
const state: STATE = reactive({
id: props.currentId,
anchorList: [],
timer: 0,
})
watch(props, (val) => {
state.anchorList = props.anchorList;
state.id = props.currentId;
})
onActivated(() => {
const personMessDetail: any = document.getElementsByClassName('ant-layout-content')[0]; // 获取滚动目标文档
personMessDetail.addEventListener('scroll', handleScroll, false); // 添加监听滚动事件
})
onDeactivated (() => {
const personMessDetail: any = document.getElementsByClassName('ant-layout-content')[0]
personMessDetail.removeEventListener('scroll', handleScroll, false); // 移除监听滚动事件
})
/** 点击锚点 */
const local = (type: string) => {
const doc: any = document.querySelector(`#${type}`);
let num = 0;
const anchorList: any = state.anchorList;
if (type === anchorList[0].id) { // 如果点击的为第一个id 滚动到顶部
num = 0;
} else {
num = doc.offsetTop - 130 // 否则滚动到距离点击的id距离目标高度+130的位置
}
const personMessDetail: any = document.getElementsByClassName('ant-layout-content')[0]
if (!personMessDetail.scrollTo) { // 目标文档滚动
personMessDetail.scrollTop = num;
} else {
personMessDetail.scrollTo(0, num);
}
state.id = type;
}
/** 监听页面滚动 */
const handleScroll = () => {
if(state.timer) {
clearTimeout(state.timer);
state.timer = 0;
}
let list: any = [];
// 获取每个id距离目标文档顶部的距离
list = state.anchorList?.map(item => {
return {
id: item.id, option: (document.querySelector(`#${item.id}`) as any).offsetTop,
}
})
let num = 0;
const personMessDetail: any = document.getElementsByClassName('ant-layout-content')[0]
state.timer = setTimeout(() => {
const aa = Number(personMessDetail.scrollTop) + 230; // 滚动到目标位置则进行id切换, + 号后面的数据可根据自身进行加减
if (aa < list[1].option) {
state.id = list[0].id;
return false;
}
for (var i = 0; i < list.length; i++) {
if (list[i].option <= aa) {
if(list[i].id !== state.id){
state.id = list[i].id;
}
}
}
}, 50)
}
return {
...toRefs(state),
local
}
}
})
</script>
<style lang="less">
.affix {
position: fixed;
width: 0;
height: 0;
top: 120px;
.anchor {
width: 160px;
padding-left: 24px;
&__basic {
width: 160px;
}
&__basic-item {
border-left: 3px solid #cccccc;
padding-left: 12px;
height: 32px;
line-height: 32px;
cursor: pointer;
}
.active {
border-left: 3px solid #1890ff;
}
}
}
</style>
标签:const,anchorList,any,state,personMessDetail,锚点,vue3,手写,id 来源: https://www.cnblogs.com/shymi/p/16277070.html