需求
使用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>