其他分享
首页 > 其他分享> > 手写vue3锚点组件

手写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