共计 5599 个字符,预计需要花费 14 分钟才能阅读完成。
问题形容
左右联动的成果,在挪动端比拟常见。比方美团外卖中的商家外卖商品抉择。常见的解决方案就是应用 better-scroll 滑屏库去实现。不过偶然 web 端的我的项目也会要做这样的左右联动的成果。本篇文章是在 vue 框架中应用原生 js 来实现相应的成果的。咱们先看一下最终的效果图:
代码附上
代码中的正文写的有思路步骤的。请依照正文思路步骤来。
<template> | |
<div id="app"> | |
<div class="top"> | |
<h2>vue 应用原生 js 实现 web 端左右滚动联动成果 </h2> | |
</div> | |
<div class="bottom"> | |
<!-- 左侧菜单栏 --> | |
<div class="bottomLeft"> | |
<div | |
class="leftItem" | |
v-for="(item0, index0) in leftArr" | |
:key="index0" | |
:class="{highLight: whichIndex == index0}" | |
@click="letItemHighLight(index0)" | |
> | |
{{item0}} | |
</div> | |
</div> | |
<!-- 左侧菜单栏对应的右侧的内容 --> | |
<div class="bottomRight" ref="wrapper"> | |
<div | |
class="bottomRightContent" | |
v-for="(item, index) in rightArr" | |
:key="index" | |
ref="item" | |
> | |
<div class="bottomRightContentHead">{{item.titleOne}}</div> | |
<div class="bottomRightContentBody"> | |
<el-col :span="8" v-for="(item2, index2) in item.titleTwo" :key="index2"> | |
<span class="circle"></span> | |
<span class="word">{{item2}}</span> | |
</el-col> | |
<!-- 革除一下浮动 --> | |
<div style="clear: both"></div> | |
</div> | |
<div class="bottomRightContentFooter"></div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</template> | |
<script> | |
export default {data() { | |
return { | |
whichIndex: 0, // 动态显示左侧菜单栏高亮 | |
leftArr: [], // 左侧菜单栏的数据 | |
rightArr: [], // 右侧详情展现对应的数据 | |
rightHeightArr: [], // 右侧每一项的高度数组 | |
rightHeightSumArr: [], // 右侧每一项的高度累加数组 | |
r: 0, // 滚动的间隔 | |
}; | |
}, | |
mounted() { | |
// 第一步,先发申请获取左右两侧的数据,用于渲染出页面,这里咱们模仿一下发申请的数据 | |
this.getLeftArrData(); | |
this.getRightArrData();}, | |
methods: {getLeftArrData() { | |
let apiLeftArr = [ | |
"西游记", | |
"三国演义", | |
"红楼梦", | |
"水浒传", | |
"龙族", | |
"幻城", | |
"犬夜叉", | |
"海贼王", | |
"一拳超人", | |
"金刚狼", | |
"钢铁侠", | |
"灭霸", | |
"雷神", | |
"贪玩蓝月", | |
"梦幻西游", | |
"王者光荣", | |
]; | |
this.leftArr = apiLeftArr; | |
}, | |
getRightArrData() { | |
let apiRightArr = [ | |
{ | |
titleOne: "西游记", | |
titleTwo: ["111", "222", "333", "444"], | |
}, | |
{ | |
titleOne: "三国演义", | |
titleTwo: ["111", "222", "333", "444", "111", "222", "333", "444"], | |
}, | |
{ | |
titleOne: "红楼梦", | |
titleTwo: ["111", "222", "333", "444", "111", "222", "333", "444"], | |
}, | |
{ | |
titleOne: "水浒传", | |
titleTwo: ["111", "222", "333", "444", "111", "222", "333", "444"], | |
}, | |
{ | |
titleOne: "龙族", | |
titleTwo: ["111", "222", "333", "444", "111", "222", "333", "444"], | |
}, | |
{ | |
titleOne: "幻城", | |
titleTwo: ["111", "222", "333", "444", "111", "222", "333", "444"], | |
}, | |
{ | |
titleOne: "犬夜叉", | |
titleTwo: ["111", "222", "333", "444", "111", "222", "333", "444"], | |
}, | |
{ | |
titleOne: "海贼王", | |
titleTwo: ["111", "222", "333", "444", "111", "222", "333", "444"], | |
}, | |
{ | |
titleOne: "一拳超人", | |
titleTwo: ["111", "222", "333", "444", "111", "222", "333", "444"], | |
}, | |
{ | |
titleOne: "金刚狼", | |
titleTwo: ["111", "222", "333", "444", "111", "222", "333", "444"], | |
}, | |
{ | |
titleOne: "钢铁侠", | |
titleTwo: ["111", "222", "333", "444", "111", "222", "333", "444"], | |
}, | |
{ | |
titleOne: "灭霸", | |
titleTwo: ["111", "222", "333", "444", "111", "222", "333", "444"], | |
}, | |
{ | |
titleOne: "雷神", | |
titleTwo: ["111", "222", "333", "444", "111", "222", "333", "444"], | |
}, | |
{ | |
titleOne: "贪玩蓝月", | |
titleTwo: ["111", "222", "333", "444", "111", "222", "333", "444"], | |
}, | |
{ | |
titleOne: "梦幻西游", | |
titleTwo: ["111", "222", "333", "444", "111", "222", "333", "444"], | |
}, | |
{ | |
titleOne: "王者光荣", | |
titleTwo: [ | |
"111", | |
"222", | |
"333", | |
"444", | |
"111", | |
"222", | |
"333", | |
"444", | |
"111", | |
"222", | |
"333", | |
"444", | |
"111", | |
"222", | |
"333", | |
"444", | |
"111", | |
"222", | |
"333", | |
"444", | |
"111", | |
"222", | |
"333", | |
"444", | |
"111", | |
"222", | |
"333", | |
"444", | |
"111", | |
"222", | |
"333", | |
"444", | |
"111", | |
"222", | |
"333", | |
"444", | |
"111", | |
"222", | |
"333", | |
"444", | |
"111", | |
"222", | |
"333", | |
"444", | |
"111", | |
"222", | |
"333", | |
"444", | |
"111", | |
"222", | |
"333", | |
"444", | |
"111", | |
"222", | |
"333", | |
"444", | |
"111", | |
"222", | |
"333", | |
"444", | |
"111", | |
"222", | |
"333", | |
"444", | |
], | |
}, | |
]; | |
this.rightArr = apiRightArr; | |
// 第二步,左右两侧有数据当前,才会把高度撑起来,才能够计算高度数组。留神先后顺序 | |
// 应用 this.$nextTick() 将回调,也就是计算两个高度数组,提早到下次 DOM 更新循环之后再计算 | |
this.$nextTick(() => {this.getTwoHeightArr(); | |
}); | |
}, | |
getTwoHeightArr() {// console.log("可能为空", this.$refs.item); | |
this.$refs.item.forEach((item) => {this.rightHeightArr.push(item["offsetHeight"]); | |
}); | |
let num = 0; | |
this.rightHeightArr.forEach((item) => { | |
num = num + item; | |
this.rightHeightSumArr.push(num); | |
}); | |
// 第三步,有了高度滚动条当前,就能够绑定滚动事件了 | |
this.bindScrollEvent();}, | |
bindScrollEvent() { | |
// 第四步,绑定滚动事件,看滚动到那个区间外面,思路就是通过右侧的区间去同步左侧的区间 | |
this.$refs.wrapper.onscroll = () => { | |
this.r = this.$refs.wrapper.scrollTop; | |
// 看看浏览器滚动的高度落到那个区间,在那个区间,就让对应的项高亮 | |
const scrollWhichIndex = this.rightHeightSumArr.findIndex((item, index) => { | |
return (this.r >= this.rightHeightSumArr[index] && | |
this.r < this.rightHeightSumArr[index + 1] | |
); | |
}); | |
console.log("所在区间",scrollWhichIndex); | |
// 初始的区间为 -1,所以还让其为第一项,即索引为 0,当用户往下滑动的时候,所以就会 | |
// 始终大于负一,所以就让其加上一和左侧的高亮项对应。if (scrollWhichIndex > -1) {this.whichIndex = scrollWhichIndex + 1;} else {this.whichIndex = 0;} | |
} | |
}, | |
// 第五步,当用户点击的时候再让其滚动,因为滚动和高亮是关联的,所以只有管制滚动,就相当于管制高亮。// 滚动的间隔就是,看用户点击的是哪个菜单项的索引,通过索引找到累加数组对应的那一项,// 也就是滚动的间隔。当为第一项的时候边界值要管制一下 | |
letItemHighLight(i) {if (this.rightHeightSumArr[i - 1] == undefined) {this.$refs.wrapper.scrollTop = 0;} else {this.$refs.wrapper.scrollTop = this.rightHeightSumArr[i - 1]; | |
} | |
}, | |
}, | |
}; | |
</script> | |
<style lang="less" scoped> | |
#app { | |
width: 100%; | |
height: 100vh; | |
.top { | |
width: 100%; | |
height: 80px; | |
text-align: center; | |
line-height: 80px; | |
background-color: #e9e9e9; | |
} | |
.bottom { | |
width: 100%; | |
height: calc(100% - 80px); | |
display: flex; | |
.bottomLeft { | |
width: 288px; | |
height: 100%; | |
background-color: #eee; | |
.leftItem { | |
width: 100%; | |
height: 50px; | |
line-height: 50px; | |
text-align: center; | |
cursor: pointer; | |
} | |
.leftItem:hover {background-color: #dfe3f1;} | |
.highLight {background: #dfe3f1;} | |
} | |
.bottomRight {width: calc(100% - 288px); | |
height: 100%; | |
box-sizing: border-box; | |
padding: 36px 36px 0 36px; | |
overflow-y: auto; | |
.bottomRightContent { | |
width: 100%; | |
box-sizing: border-box; | |
padding-bottom: 36px; | |
.bottomRightContentHead { | |
height: 25px; | |
font-family: PingFang SC; | |
font-style: normal; | |
font-weight: 600; | |
font-size: 24px; | |
line-height: 25px; | |
text-transform: capitalize; | |
color: rgba(0, 0, 0, 0.85); | |
margin-bottom: 32px; | |
} | |
.bottomRightContentBody { | |
.el-col { | |
position: relative; | |
margin-bottom: 18px; | |
.circle { | |
display: inline-block; | |
width: 6px; | |
height: 6px; | |
background: #4677f6; | |
border-radius: 50%; | |
position: absolute; | |
top: 8px; | |
left: 0; | |
} | |
.word { | |
margin-left: 12px; | |
font-family: PingFang SC; | |
font-style: normal; | |
font-weight: normal; | |
font-size: 14px; | |
color: #4677f6; | |
cursor: pointer; | |
} | |
.word:hover {text-decoration: underline;} | |
.topPlace { | |
position: absolute; | |
top: 1px; | |
margin-left: 8px; | |
} | |
} | |
} | |
.bottomRightContentFooter { | |
height: 1px; | |
width: 100%; | |
margin-top: 14px; | |
background-color: #e9e9e9; | |
} | |
} | |
} | |
} | |
} | |
</style> |
总结
实现形式有很多种,我写的这种仅供参考。如有我写的不清晰的,欢送私信或文章评论。与大家共同进步
正文完
发表至: javascript
2021-03-25