vue3实现目录导航内容联动

109 阅读2分钟

最近有个需求:导航目录跟内容联动,发现网上相关内容比较少,我就花了点时间总结一下,先看效果

完整代码仓库地址

Video_20231122205313 00_00_00-00_00_30.gif

样式优点丑,这个不纠结,咱们主要看功能

样式

这里主要有右侧目录导航栏和内容样式,样式混入的知识点,请看文章

<template>
  <div class="container" ref="container">
    <div ref="boxs" class="box" v-for="c in colors" :key="c" :id="c" :style="{background: c}">
      <h2>{{c}}</h2>
    </div>
    <div class="directory">
      <p v-for="c in colors" :key="c" :style="{background: (currentItem === c && c) || 'black'}">{{ c }}</p>
    </div>
  </div>
</template>
<script lang="ts" setup>
const colors = ['red', 'green', 'blue', 'yellow', 'skyblue', 'purple', 'brown', 'pink']
const currentItem = ref('red')
const container = ref<HTMLDivElement | null>(null)
const boxs = ref<HTMLDivElement[] | null>([])
</script>
<style lang="scss" scoped>
.container {
  width: 100%;
  height: 100%;
  position: relative;
  // @include overflow-overlay(); // 这@include是混入等价代码下面两行
  overflow: auto;
  overflow: overlay;
  // scroll-behavior: smooth;
  .box {
    width: 100%;
    height: 600px;
  }
  .directory {
    background-color: #eee;
    border: 1px solid #fff;
    position: fixed;
    right: 0;
    top: 50%;
    transform: translateY(-50%);
    display: flex;
    flex-direction: column;
    padding: 4px;
    color: white;
    border-radius: 4px;
    > p {
      width: 100%;
      height: 24px;
      line-height: 24px;
      padding: 0 4px;
      border-radius: 4px;
      text-align: center;
      cursor: pointer;
    }
  }
}
</style>

目录导航 => 内容联动

给目录导航注册点击事件,通过scrollIntoView方法使得内容到指定位置

  1. 点击导航高亮自己
  2. 找到导航对应的定位的元素
  3. 将元素置顶
<template>
  <div class="container" ref="container">
    <div ref="boxs" class="box" v-for="c in colors" :key="c" :id="c" :style="{background: c}">
      <h2>{{c}}</h2>
    </div>
    <div class="directory">
      <p @click="scrollToItem(c)" v-for="c in colors" :key="c" :style="{background: (currentItem === c && c) || 'black'}">{{ c }}</p>
    </div>
  </div>
</template>
<script lang="ts" setup>
const colors = ['red', 'green', 'blue', 'yellow', 'skyblue', 'purple', 'brown', 'pink']
const currentItem = ref('red')
const scrollToItem = (item: string) => {
  // 1. 点击导航高亮自己
  currentItem.value = item
  // 2. 找到导航对应的定位的元素
  const element: HTMLElement | null = document.getElementById(item)
  // 3. 将元素置顶
  element?.scrollIntoView()
}
</script>

内容 =>目录导航联动

通过注册scroll事件计算containerscrollTop跟每个内容的offsetTop比较设置currentItem,关于scrollTopoffsetTop,请看文章

  1. 注册滚动事件
  2. 销毁滚动事件
  3. 拿到container滚动卷曲的高度scrollTop
  4. 如果container的scrollTop >= 滚动的元素的offsetTop,代表该元素到达顶部,这个时候高亮导航栏
<script lang="ts" setup>
const colors = ['red', 'green', 'blue', 'yellow', 'skyblue', 'purple', 'brown', 'pink']
const currentItem = ref(colors[0])

const scrollToItem = (item: string) => {
  // 1. 点击导航高亮自己
  currentItem.value = item
  // 2. 找到导航对应的定位的元素
  const element: HTMLElement | null = document.getElementById(item)
  // 3. 将元素置顶
  element?.scrollIntoView()
}

const container = ref<HTMLDivElement | null>(null)
onMounted(() => {
  // 1. 注册滚动事件
  container.value?.addEventListener('scroll', onScroll)
})
onBeforeUnmount(() => {
  // 2. 销毁滚动事件
  container.value?.removeEventListener('scroll', onScroll)
})
const boxs = ref<HTMLDivElement[] | null>([])
const onScroll = () => {
  // 3. 拿到container滚动卷曲的高度scrollTop
  const scrollTop = container.value?.scrollTop || 0
  boxs.value?.forEach(v => {
    // 4. 如果container的scrollTop >= 滚动的元素的offsetTop,代表该元素到达顶部,这个时候高亮导航栏
    if (scrollTop >= v.offsetTop) {
      currentItem.value = v.id
    }
  })
}
</script>