菜单与内容联动——vue3实现

需求

使用vue3实现一个常见的二级联动功能:
点击菜单能跳转到对应位置、滚动位置应激活相应菜单

如果需不要联动,只需要点击跳转,可以通过 id<a> 标签实现
<a href="#A">A</a>
<div id="A"/>

实现

思路:
页面加载完毕后,计算各区域位置
点击菜单跳转:跳转至对应区域位置
滚动位置监听:监听可滚动的父元素,计算当前位置对应的区域,激活菜单

如果区域内容动态更新,高度不定,可在动态更新后重新计算各区域位置
手动调用父元素的 scrollTo 方法,可更精确得设置滚动位置、滚动效果
滚动触发过于频繁,可使用节流函数降低触发频率

在线演示

<script setup>
  import {
    
     ref, onMounted, onBeforeUnmount } from 'vue'
  // 菜单、区域
  const links = ref([
    {
    
     label: 'Home', id: 'home', top: 0, active: true },
    {
    
     label: 'Platform', id: 'platform', top: 0 },
    {
    
     label: 'Career', id: 'career', top: 0 },
    {
    
     label: 'Contact', id: 'contact', top: 0 },
    {
    
     label: 'About', id: 'about', top: 0 }
  ])
  // 滚动父元素
  const scrollWrap = ref()
  onMounted(() => {
    
    
    // 各区域位置计算
    links.value.forEach(s => {
    
    
      s.top = document.getElementById(s.id).offsetTop
    })
    console.log(links.value)
    // 监听 scroll 事件
    scrollWrap.value?.addEventListener('scroll', scrollThrottleHandler)
  })

  onBeforeUnmount(() => {
    
    
    // 移除 scroll 监听
    scrollWrap.value?.removeEventListener('scroll', scrollThrottleHandler)
  })

// 滚动处理
const scrollHandler = (e) => {
    
    
  let scrollTop = e.target.scrollTop, // 当前滚动位置
      idx = 0 // 当前滚动位置对应的区域下标
  for(let len = links.value.length, i = len - 1; i >= 0; i--) {
    
    
    if(scrollTop >= links.value[i].top - 63 - 2) {
    
    
      idx = i
      break
    } else {
    
    
      continue
    }
  }
  const oldIdx = links.value.findIndex(s => s.active)
  if(idx !== oldIdx) {
    
    
    links.value[oldIdx].active = false
    links.value[idx].active = true
  }
}
// 节流函数
function throttle(fn, threshhold = 250, _this) {
    
    
  let last
  let timer
  return function () {
    
    
    let context = _this
    let args = arguments
    let now = +new Date()
    if (last && now < last + threshhold) {
    
    
      clearTimeout(timer)
      timer = setTimeout(function () {
    
    
        last = now
        context ? fn.apply(context, args) : fn(...args)
      }, threshhold)
    } else {
    
    
      last = now
      context ? fn.apply(context, args) : fn(...args)
    }
  }
}
// 节流处理
const scrollThrottleHandler = throttle(scrollHandler, 300)

// 滚动到指定区域
function scrollToSection(top) {
    
    
  scrollWrap.value?.scrollTo({
    
    
    top: top - 63,
    left: 0,
    behavior: 'smooth'
  })
}
</script>

<template>
  <div class="nav">
    <span
      :class="['menu-item', { active: !!link.active }]"
      v-for="link in links"
      :key="link.label"
      v-html="link.label"
      @click="scrollToSection(link.top)"
    />
  </div>
  <div class="scroll-wrap" ref="scrollWrap">
    <div class="scroll-view">
      <div
        class="container"
        v-for="link in links"
        :key="link.label"
        :id="link.id"
      >
        <span>{
    
    {
    
    link.label}}</span>
      </div>
    </div>
  </div>
</template>

<style>
html,body {
    
    margin:0}
body {
    
    
  margin: 10px;
  border: 1px solid #ccc;
}
.scroll-wrap {
    
    
  height: 500px;
  padding: 10px;
  overflow: auto;
  background-color: #f5f7f9;
}
.scroll-view {
    
    
  min-height: 3000px;
}
.nav {
    
    
  padding: 10px;
  background-color: #fff;
  box-shadow: rgb(221 221 221 / 50%) 0px 2px 4px 0px;
}
.menu-item {
    
    
  padding-right: 10px;
  font-size: 16px;
  color: #222;
  line-height: 22px;
  cursor: pointer;
}
.menu-item + .menu-item {
    
    
  padding-left: 10px;
  border-left: 1px solid #ccc;
}
.menu-item.active {
    
    color:#387FE5}
.container {
    
    
  padding: 10px;
  border-radius: 6px;
  background-color: #fff;
  min-height: 300px;
}
.container + .container {
    
    
  margin-top: 10px;
}
</style>

猜你喜欢

转载自blog.csdn.net/ymzhaobth/article/details/125787286
今日推荐