在 Web 应用中,锚点导航是一种常见的 UI 交互模式,尤其适用于文档、目录式页面或单页应用(SPA)。本文将使用 Vue3 实现一个带有锚点功能的页面:左侧是 Tab 控制菜单,右侧是可滚动的内容区域,选中的内容会自动对齐到顶部。
功能需求
左侧 Tab 控制导航,点击后滚动到对应内容。
右侧内容区域可滚动,滚动时 Tab 自动高亮。
内容不足时,仍然可以滚动到顶部
关键点
在 onScroll
方法中,我们遍历 sectionRefs
计算当前滚动位置,并更新 activeIndex
,保证 Tab 与内容联动。
添加 spacer
作为占位元素,确保即使最后一个 Section 也能正确滚动到顶部。
效果图
完整代码示例
xml
<template>
<div class="container">
<!-- 左侧Tab导航 -->
<div class="tabs">
<div
v-for="(section, index) in sections"
:key="index"
:class="['tab', { active: activeIndex === index }]"
@click="scrollToSection(index)"
>
{{ section.name }}
</div>
</div>
<!-- 右侧内容区域,可滚动 -->
<div class="content" ref="contentRef" @scroll="onScroll">
<div
v-for="(section, index) in sections"
:key="index"
:ref="el => sectionRefs[index] = el"
class="section"
>
<h2>{{ section.name }}</h2>
<p>{{ section.content }}</p>
</div>
<!-- 占位符,确保最后一个Section也能滚动到顶部 -->
<div class="spacer"></div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, nextTick } from 'vue';
// 定义章节数据,每个章节包含标题和内容
const sections = ref([
{ name: 'Section 1', content: 'This is section 1' },
{ name: 'Section 2', content: 'This is section 2' },
{ name: 'Section 3', content: 'This is section 3' },
{ name: 'Section 4', content: 'This is section 4' }
]);
// 存储各个section的DOM引用
const sectionRefs = ref([]);
// 右侧滚动区域的引用
const contentRef = ref(null);
// 记录当前选中的Tab索引
const activeIndex = ref(0);
// 处理点击Tab,滚动到对应的Section顶部
const scrollToSection = (index) => {
if (sectionRefs.value[index]) {
const contentTop = contentRef.value.offsetTop;
const targetOffset = sectionRefs.value[index].offsetTop - contentTop;
contentRef.value.scrollTo({ top: targetOffset, behavior: 'smooth' });
}
};
// 监听滚动,动态更新当前选中的Tab
const onScroll = () => {
const scrollTop = contentRef.value.scrollTop;
let currentActive = 0;
sectionRefs.value.forEach((el, index) => {
if (el.offsetTop - contentRef.value.offsetTop <= scrollTop) {
currentActive = index;
}
});
activeIndex.value = currentActive;
};
</script>
<style scoped>
.container {
display: flex;
height: 100vh;
}
/* 左侧Tab栏样式 */
.tabs {
width: 200px;
border-right: 1px solid #ddd;
padding: 10px;
}
.tab {
padding: 10px;
cursor: pointer;
}
.tab.active {
font-weight: bold;
color: blue;
}
/* 右侧内容区域样式 */
.content {
flex: 1;
overflow-y: auto;
height: 100vh;
padding: 20px;
position: relative;
}
/* 章节样式 */
.section {
height: 300px;
padding: 20px;
border-bottom: 1px solid #ddd;
}
/* 空间占位,保证最后一个Section能滚动到顶部 */
.spacer {
height: 100vh;
}
</style>