实现效果说明
整体布局,内容和导航联动起来,内容滚动时电梯导航栏跟随内容指定对应楼层,点击导航,滚动到指定内容。
代码实现比较简单,就那么几行,就不多废话了,上代码。
<template>
<div class="main">
<!-- 内容区域 每个模块的id要与menu的id一一对应-->
<section class="content" id="gonglu">
<div class="header">
公路运输
</div>
</section>
<section class="content" id="tielu">
<div class="header">
铁路运输
</div>
</section>
<section class="content" id="shuilu">
<div class="header">
水路运输
</div>
</section>
<section class="content" id="cangchu">
<div class="header">
仓储统配
</div>
</section>
<!-- 侧边栏 -->
<ul class="sidebar">
<li v-for="item in menu" :key="item.name" :class="{ active: 1 === item.active }">
<a href="javascript:;" @click="scrollTo(item)">{{ item.name }}</a>
</li>
</ul>
</div>
</template>
<script>
export default {
props: {},
data() {
return {
menu: [
{
name: '公路运输',
id: 'gonglu',
active: 0, // 用来判断侧边栏的激活状态,为1则代表激活,class的active为true。
},
{
name: '铁路运输',
id: 'tielu',
active: 0,
},
{
name: '水路运输',
id: 'shuilu',
active: 0,
},
{
name: '仓储统配',
id: 'cangchu',
active: 0,
}
]
}
},
mounted() {
//获取每个模块的参数,因为模块在整页渲染完毕后,offsetTop和scrollHeight值已经是确立的,所以在滚动监听之前可以先获取,暂存在menu中。
this.menu.forEach(item => {
item.offsetTop = document.getElementById(item.id).offsetTop;
item.scrollHeight=document.getElementById(item.id).scrollHeight;
})
// 监听滚动事件
window.addEventListener('scroll', this.onScroll)
},
destroy() {
// 必须移除监听器,不然当该vue组件被销毁了,监听器还在就会出错
window.removeEventListener('scroll', this.onScroll)
},
methods: {
// 滚动监听器
onScroll() {
// 获取浏览器窗口高度
let clientHeight=window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
// 获取当前文档流的 scrollTop
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
// 屏幕垂直中线
let screenMiddle=scrollTop+clientHeight/2;
this.menu.forEach(item=>{
//以浏览器窗口垂直中线为准,中线在内容模块的上边界至下边界之间,导航栏都认为在该楼层
if(screenMiddle-item.offsetTop>=0&&screenMiddle-item.offsetTop<item.scrollHeight){
item.active=1;
}else{
item.active=0;
}
})
},
// 跳转到指定索引的元素
scrollTo(item) {
// 获取目标的 offsetTop
// css选择器是从 1 开始计数,我们是从 0 开始,所以要 +1
const targetOffsetTop = document.getElementById(item.id).offsetTop
// 获取当前 offsetTop
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop
// 定义一次跳 50 个像素,数字越大跳得越快,但是会有掉帧得感觉,步子迈大了会扯到蛋
const STEP = 50
// 判断是往下滑还是往上滑
if (scrollTop > targetOffsetTop) {
// 往上滑
smoothUp()
} else {
// 往下滑
smoothDown()
}
// 定义往下滑函数
function smoothDown() {
// 如果当前 scrollTop 小于 targetOffsetTop 说明视口还没滑到指定位置
if (scrollTop < targetOffsetTop) {
// 如果和目标相差距离大于等于 STEP 就跳 STEP
// 否则直接跳到目标点,目标是为了防止跳过了。
if (targetOffsetTop - scrollTop >= STEP) {
scrollTop += STEP
} else {
scrollTop = targetOffsetTop
}
document.body.scrollTop = scrollTop
document.documentElement.scrollTop = scrollTop
// 关于 requestAnimationFrame 可以自己查一下,在这种场景下,相比 setInterval 性价比更高
requestAnimationFrame(smoothDown)
}
}
// 定义往上滑函数
function smoothUp() {
if (scrollTop > targetOffsetTop) {
if (scrollTop - targetOffsetTop >= STEP) {
scrollTop -= STEP
} else {
scrollTop = targetOffsetTop
}
document.body.scrollTop = scrollTop
document.documentElement.scrollTop = scrollTop
requestAnimationFrame(smoothUp)
}
}
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style lang="scss" scoped>
.main {
padding: 30px;
padding-right: 240px;
.content {
height: 70vh;
background: #eee;
border-radius: 8px;
margin-bottom: 15px;
}
.header {
padding: 15px;
font-size: 20px;
font-weight:600;
color:black
}
}
.sidebar {
position: fixed;
top: 50%;
right: 50px;
width: 168px;
overflow: hidden;
background-color: #fff;
border-radius: 8px;
padding: 0;
transform: translateY(-50%);
box-shadow: 0px 1px 10px 0px rgba(0, 0, 0, 0.05), 0px 3px 5px 0px rgba(0, 0, 0, 0.06), 0px 2px 4px -1px rgba(0, 0, 0, 0.04);
li {
line-height: 40px;
text-align: center;
list-style: none;
&:not(:last-child) {
border-bottom: #f5f5f5 solid 1px;
}
&.active {
color: #fff;
background-color: #ff8000;
}
a {
display: block;
width: 100%;
height: 100%;
color: #333;
text-decoration: none;
}
}
}
</style>
网友评论