功能描述
点击导航跳到对应模块的起始位置,并且高亮点击的导航;
滚动到相应的模块时,对应的导航也自动高亮;
效果展示
注意事项
- 一定要明确哪个是要滚动的盒子;
- 滚动的高度要减去导航栏的高度;
- 当前在导航1,点击导航4时,会出现导航2、导航3和导航4依次高亮的现象,定义变量判断当前为点击时不监听滚动事件即可;
isScroll.value = false
核心代码
onMounted(() => {nextTick(() => {// 监听滚动的盒子的滚动事件document.getElementById('scrollBox').addEventListener("scroll", onScroll);})
})onBeforeUnmount(() => {document.getElementById('scrollBox').removeEventListener('scroll', onScroll)
})const scrollToAnchor = (id, index) => {activeIndex.value = indexvar anchor = document.getElementById(id);var scrollBox = document.getElementById('scrollBox')nextTick(() => {scrollBox.scrollTo({top: anchor.offsetTop - 30,behavior: 'smooth'});})isScroll.value = false// 点击时暂停页面的滚动监听事件,防止点击时导航高亮出现走马灯效果;// 此处的定时器存在缺陷,点击完2秒之内滚动还是会偶现走马灯现象;let timeId = '';clearTimeout(timeId);timeId = setTimeout(() => {isScroll.value = true;}, 2000);
};// 滚动监听器
const onScroll = ()=> {if(!isScroll.value) return// 获取所有锚点元素const navContents = document.querySelectorAll('.container .section')// 所有锚点元素的 offsetTopconst offsetTopArr = []navContents.forEach(item => {offsetTopArr.push(item.offsetTop-30)})// 获取当前文档流的 scrollTopconst scrollTop = document.getElementById('scrollBox').scrollTop// 定义当前点亮的导航下标let navIndex = 1for (let n = 1; n <= offsetTopArr.length; n++) {// 如果 scrollTop 大于等于第 n 个元素的 offsetTop 则说明 n-1 的内容已经完全不可见// 那么此时导航索引就应该是 n 了if (scrollTop >= offsetTopArr[n-1]) {navIndex = n}}// 把下标赋值给 vue 的 dataactiveIndex.value = navIndex
}
完整代码
<template><div class="box" id="scrollBox" v-loading="loading"><div class="fixed-box"><div v-for="(i) in defaultTabs" :class="{active_anchor: activeIndex === i}"><a @click="scrollToAnchor(`section${i}`, i)"><span v-if="i == 1">导航1</span><span v-if="i == 2">导航2</span><span v-if="i == 3">导航3</span><span v-if="i == 4">导航4</span><span v-if="i == 5">导航5</span></a></div></div><div class="container" id="printcontent"><div class="section" id="section1"><h3>日历</h3><el-calendar v-model="value" /></div><div class="section" id="section2"><h3>带边框列表</h3><el-descriptions class="margin-top" title="" :column="4" border><template v-for="i in 4"><el-descriptions-item label="Username">kooriookami</el-descriptions-item><el-descriptions-item label="Telephone">18100000000</el-descriptions-item><el-descriptions-item label="Place" :span="2">Suzhou</el-descriptions-item><el-descriptions-item label="Remarks">123123</el-descriptions-item><el-descriptions-item label="Username">一一一</el-descriptions-item><el-descriptions-item label="Remarks">123123</el-descriptions-item><el-descriptions-item label="Remarks">-</el-descriptions-item><el-descriptions-item label="Remarks">-</el-descriptions-item><el-descriptions-item label="Remarks">-</el-descriptions-item></template></el-descriptions></div><div class="section" id="section3"><h3>普通表格</h3><el-table :data="tableData" style="width: 100%"><el-table-column prop="date" label="Date" width="180" /><el-table-column prop="name" label="Name" width="180" /><el-table-column prop="address" label="Address" /></el-table></div><div class="section" id="section4"><h3>普通卡片</h3><el-card class="box-card"><template #header><div class="card-header"><span>Card name</span><el-button class="button" text>Operation button</el-button></div></template><div v-for="o in 10" :key="o" class="text item">{{ 'List item ' + o }}</div><template #footer>Footer content</template></el-card></div></div></div>
</template><script setup>
import { onMounted, ref, nextTick, onBeforeUnmount } from 'vue'
const loading = ref(false)
const value = ref(new Date())
const defaultTabs = ref([1, 2, 3, 4, 5])
const activeIndex = ref(1)
const isScroll = ref(true) // 点击导航栏时,暂时停止监听页面滚动
const tableData = [{date: '2016-05-03',name: 'Tom',address: 'No. 189, Grove St, Los Angeles',},{date: '2016-05-02',name: 'Tom',address: 'No. 189, Grove St, Los Angeles',},{date: '2016-05-04',name: 'Tom',address: 'No. 189, Grove St, Los Angeles',},{date: '2016-05-01',name: 'Tom',address: 'No. 189, Grove St, Los Angeles',},{date: '2016-05-04',name: 'Tom',address: 'No. 189, Grove St, Los Angeles',},{date: '2016-05-01',name: 'Tom',address: 'No. 189, Grove St, Los Angeles',},{date: '2016-05-04',name: 'Tom',address: 'No. 189, Grove St, Los Angeles',},{date: '2016-05-01',name: 'Tom',address: 'No. 189, Grove St, Los Angeles',},{date: '2016-05-04',name: 'Tom',address: 'No. 189, Grove St, Los Angeles',},{date: '2016-05-01',name: 'Tom',address: 'No. 189, Grove St, Los Angeles',},{date: '2016-05-04',name: 'Tom',address: 'No. 189, Grove St, Los Angeles',},{date: '2016-05-01',name: 'Tom',address: 'No. 189, Grove St, Los Angeles',},
]onMounted(() => {nextTick(() => {document.getElementById('scrollBox').addEventListener("scroll", onScroll);})
})onBeforeUnmount(() => {document.getElementById('scrollBox').removeEventListener('scroll', onScroll)
})const scrollToAnchor = (id, index) => {activeIndex.value = indexvar anchor = document.getElementById(id);var scrollBox = document.getElementById('scrollBox')nextTick(() => {scrollBox.scrollTo({top: anchor.offsetTop - 30,behavior: 'smooth'});})// 点击时暂停页面的滚动监听事件,防止点击时导航高亮出现走马灯效果;// 此处的定时器存在缺陷,点击完2秒之内滚动还是会偶现走马灯现象;isScroll.value = falselet timeId = '';clearTimeout(timeId);timeId = setTimeout(() => {isScroll.value = true;}, 2000);
};// 滚动监听器
const onScroll = ()=> {if(!isScroll.value) return// 获取所有锚点元素const navContents = document.querySelectorAll('.container .section')// 所有锚点元素的 offsetTopconst offsetTopArr = []navContents.forEach(item => {offsetTopArr.push(item.offsetTop-30)})// 获取当前文档流的 scrollTopconst scrollTop = document.getElementById('scrollBox').scrollTop// 定义当前点亮的导航下标let navIndex = 1for (let n = 1; n <= offsetTopArr.length; n++) {// 如果 scrollTop 大于等于第 n 个元素的 offsetTop 则说明 n-1 的内容已经完全不可见// 那么此时导航索引就应该是 n 了if (scrollTop >= offsetTopArr[n-1]) {navIndex = n}}// 把下标赋值给 vue 的 dataactiveIndex.value = navIndex
}</script>
<style scoped lang="scss">
.box{// width: 98%;margin: auto;height: 100%;overflow: auto;
}
.fixed-box{width: calc(100% - 200px);background: white;position: fixed;top: 84px;display: flex;justify-content: space-between;align-items: center;z-index: 99;padding: 5px 30px;box-sizing: border-box;box-shadow: 1px 1px 1px #ccc;span {padding: 0 10px;list-style: none;}
}
.container {padding-top: 15px;position: relative;
}
.section {margin: 20px 0;
}
:deep(.el-tabs ){--el-tabs-header-height: 50px;
}
.active_anchor {color: #409eff;
}
</style>