瞄点组件,无耦合,随便用

158 阅读1分钟

效果

使用方法:

1. 组件引入,进行绑定
// 右侧锚点
        <BAnchor          class="tab-nav"          ref="anchor"          container=".content-info" // 绑定监听的容器          target=".main-box" // 绑定滚动的容器          :targetOffset="50"        ></BAnchor>
2.左侧标题
  <div class="form-title">    <h3 class="c-title">基本信息</h3>    <el-divider />    <span></span>  </div>
/*js*/const anchor = ref()onMounted(async () => {  nextTick(() => {    anchor.value?.updateNav()  })})


组件

<template>  <div class="b-anchor">    <i class="toc-line"></i>    <nav class="toc-content">      <span class="toc-content-heading" v-if="title">{{ title }}</span>      <ul class="toc-items">        <li          v-for="(v, k) in navs"          :key="k"          :class="[{ active: active == k }, d1((v as HTMLDivElement).nodeName)]"          @click="scrollTo(k as number)"        >          {{ (v as HTMLDivElement).innerText }}        </li>      </ul>    </nav>  </div></template><script setup lang="ts" name="BAnchor">import { debounce } from 'lodash'import { onMounted, onUnmounted, ref } from 'vue'interface Props {  // 指定监听的容器  container: string  // 滚动容器  target?: string  // 标题  title?: string  // 距离窗口顶部达到指定偏移量  targetOffset?: number}const props = withDefaults(defineProps<Props>(), {  targetOffset: 0})const active = ref(0)const navs = ref<any>({})const target = ref({} as Element | Window)const d1 = (val: string) => {  switch (val) {    case 'H1':    case 'H2':      return 'd2'    case 'H3':      return 'd3'    default:      return 'd4'  }}// 滚动监听器const onScroll = debounce(() => {  // 所有锚点元素的 offsetTop  console.log('v', 22)  const offsetTopArr: number[] = []  navs.value.forEach((v: any) => {    offsetTopArr.push(v.offsetTop)  })  const scroll =    target.value instanceof Element ? target.value.scrollTop : undefined  // 获取当前文档流的 scrollTop  const scrollTop =    scroll || document.documentElement.scrollTop || document.body.scrollTop  // 定义当前点亮的导航下标  offsetTopArr.forEach((v, k) => {    if (scrollTop >= v - 10 - props.targetOffset) {      active.value = k    }  })}, 250)// 跳转到指定索引的元素const scrollTo = (k: number) => {  const tar = navs.value.item(k)  if (props.target) {    target.value.scrollTo({      top: tar.offsetTop - props.targetOffset,      behavior: 'smooth'    })  } else {    document.documentElement.scrollTo({      top: tar.offsetTop - props.targetOffset,      behavior: 'smooth'    })  }  console.log('scrollTo', k, navs.value, tar.offsetTop)}const updateNav = () => {  // 获取所有锚点元素  navs.value = document    .querySelector(props.container)    ?.querySelectorAll('h1,h2,h3,h4,h5,h6')}onMounted(() => {  if (props.target) {    target.value = document.querySelector(props.target) as Element  } else {    target.value = window  }  // 获取所有锚点元素  navs.value = document    .querySelector(props.container)    ?.querySelectorAll('h1, h2, h3, h4, h5, h6')  target.value.addEventListener('scroll', onScroll)})onUnmounted(() => {  target.value.removeEventListener('scroll', onScroll)})defineExpose({ updateNav })</script><style lang="scss" scoped>@mixin ellipsis {  text-overflow: ellipsis;  overflow: hidden;  white-space: nowrap;}.b-anchor {  position: fixed;  width: inherit;  height: inherit;  overflow-y: auto;  overflow-x: hidden;}.toc-line {  position: absolute;  top: 0;  left: 0;  bottom: 0;  width: 1px;  background: #dfe1e8;  z-index: -1;}.toc-content {  .toc-content-heading {    font-size: 12px;    font-weight: 600;    text-transform: uppercase;    margin: 0;  }  .toc-items {    list-style: none;    padding: 0;    li {      position: relative;      font-size: 14px;      line-height: 28px;      color: #5e6373;      font-weight: 400;      text-indent: 20px;      @include ellipsis;      cursor: pointer;      + li {        margin-top: 8px;      }    }    li:hover {      background: #f7f8fa;      border-radius: 4px;      color: #409eff;      // border-left: 1px solid #dfe1e8;    }    .active {      color: #409eff;    }    .d2 {      font-weight: 600;    }    .d3 {      padding-left: 15px;    }    .d4 {      padding-left: 35px;    }    .active::before {      content: '';      position: absolute;      left: 0;      background-color: #409eff;      border-radius: 1px;      width: 2px;      height: 28px;      top: 0px;    }  }}</style>