效果图
用到的插件 — better-scroll
在项目中引入两个包
- better-scroll 滚动的核心库
- @better-scroll/mouse-wheel 该插件的作用是可以使用鼠标滚动
代码实现
使用vue2结合better-scroll实现菜单与商品左右联动
这里截取better-scroll官网的说明,有兴趣可以去阅读官网:https://www.wenjiangs.com/doc/2fh58adz
- html部分
<!-- 注意事项
1.BetterScroll 默认处理容器的第一个子元素的滚动,其它的元素都会被忽略。按照我下面的HTML嵌套结构
2.必须给容器设置高度,且overflow为hidden
3.如果容器外层还有父元素,父元素也必须设置高度
4.满足以上条件better-scroll的滚动效果才能生效
-->
<!-- 左侧导航栏 -->
<div ref="siderMenu" class="pkg-content-detail__left">
<div class="sider-menu">
<div
:key="index"
:class="index === activeKey ? 'active' : ''"
v-for="(menu, index) in menuList"
ref="menuItem"
class="menu-item"
@click="handleClickMenu(index)"
>
<span>{{ menu.name}}</span>
</div>
<!-- 占位 -->
<div :style="{ height: calc(100% - customHeight) }" v-if="menuList.length < 10" style="background: #f7f7f7;"></div>
</div>
</div>
<!-- 右侧商品区域 -->
<div ref="goodsContainer" class="pkg-content-detail__right">
<div ref="pkgItemContainer" class="content">
<div
:key="index"
v-for="(good, index) in goodsList"
class="pkg-content-detail__right-item"
>
<!-- 该组件为商品的详情组件,可根据自己的需求自行封装 -->
<add-goods-item :goodsGroup="good"/>
</div>
<!-- 这里是滑动到丢展示的文字,样式可以自行调整 -->
<div style="padding: 30px 0 310px; color: #666; font-size: 14px; text-align: center;">已经到底啦</div>
</div>
</div>
- js部分
// 引入插件
import BScroll from 'better-scroll'
import MouseWheel from '@better-scroll/mouse-wheel'
BScroll.use(MouseWheel)
data() {
return {
menuList: [
{
name: '推荐'
},
{
name: '中医科检查室'
},
{
name: '人体成分'
},
{
name: '核医学科'
},
{
name: '骨密度室'
},
{
name: '病理科'
},
{
name: '动脉硬化'
},
{
name: '心电室'
},
{
name: '口腔科'
},
{
name: '化验室'
}
], // 菜单列表
goodsList: [], // 商品列表 --- 可根据自己的需求添加商品数据
goodsTops: [], // 左侧菜单对应的商品高度
menuTops: [], // 左侧菜单的高度
goodsScrollY: 0, // 商品滚动条高度
menuScrollY: 0, // 左侧菜单滚动条高度
goodsScroll: null, // 商品滚动条容器
menuScroll: null, // 菜单滚动条容器
}
},
mounted() {
this.$nextTick(() => {
this._initScroll()
this._initTops()
})
},
watch: {
activeKey: {
immediate: true,
handler (newVal, oldVal) {
// 这里是让商品滚动的过程中也让菜单跟着滚动
if (newVal > 5 && newVal !== oldVal) {
this.menuScroll.scrollTo(0, -this.menuTops[this.activeKey - 6], 300)
} else if (newVal <= 5 && this.menuScrollY > 0) {
this.menuScroll.scrollTo(0, 56, 300)
}
// 为当前点击菜单的上一个兄弟与下一个兄弟添加圆角
this.$nextTick(() => {
let arr = Array.from(this.$refs.menuItem)
arr.forEach(item => {
item.style.borderRadius = '0'
if (!item.className.includes('active')) {
item.style.background = '#f7f7f7'
} else {
item.style.background = '#ffffff'
}
})
if (arr[newVal].nextSibling && arr[newVal].nextSibling.className?.includes('menu-item')) {
arr[newVal].nextSibling.style.borderRadius = '0 12px 0 0'
}
if (arr[newVal].previousSibling) {
arr[newVal].previousSibling.style.borderRadius = '0 0 12px 0'
}
})
}
}
},
computed: {
// 计算当前分类的下标
activeKey () {
// 得到条件数据
let { goodsScrollY, goodsTops } = this
// 根据条件计算产生一个结果
// findIndex方法常用来查找数组中满足条件的第一项元素的下标
// 返回的是满足条件的第一项元素的下标,这要注意的是findIndex会给数组中的每一项执行一个函数来判断是否满足表达式,如果满足条件后,剩下的元素则不再执行
let index = goodsTops.findIndex((top, index) => {
// scrollY要大于等于当前top值,还要小于下一个top
return goodsScrollY >= top && goodsScrollY < goodsTops[index + 1]
})
// 返回结果
if (index === -1) {
index = 0
}
return index
},
},
methods: {
// 初始化滚动
_initScroll () {
// 商品滚动配置
this.goodsScroll = new BScroll(this.$refs.goodsContainer, {
// 惯性滑动被触发
probeType: 3,
click: true,
bounce: false,
// 开启鼠标滚动
mouseWheel: {
speed: 20,
invert: false,
easeTime: 300
}
})
this.goodsScroll.on('scroll', ({ y }) => {
// math.abs绝对值
this.goodsScrollY = Math.abs(y)
})
this.goodsScroll.on('scrollEnd', ({ y }) => {
this.goodsScrollY = Math.abs(y)
})
// 菜单栏滚动配置
this.menuScroll = new BScroll(this.$refs.siderMenu, {
// 惯性滑动被触发
probeType: 3,
click: true,
bounce: false,
// 开启鼠标滚动
mouseWheel: {
speed: 20,
invert: false,
easeTime: 300
}
})
this.menuScroll.on('scroll', ({ y }) => {
// math.abs绝对值
this.menuScrollY = Math.abs(y)
})
this.menuScroll.on('scrollEnd', ({ y }) => {
this.menuScrollY = Math.abs(y)
})
},
// 初始化tops
_initTops () {
// 商品的tops --- 也就是每个菜单对应的商品所占的高度:[0, 141, 337, 533, 729, 925, 1218, 1414, 1635, 1831, 11377, 11573, 13371, 13689, 13885, 14106, 16145, 17361, 17557, 18069, ......]
const goodsTops = []
let top1 = 0
goodsTops.push(top1)
// 找到所有分类的item
const goodsItems = this.$refs.pkgItemContainer.getElementsByClassName('pkg-content-detail__right-item')
Array.prototype.slice.call(goodsItems).forEach(item => {
top1 = top1 + item.clientHeight
goodsTops.push(top1)
})
this.goodsTops = goodsTops
// 菜单的tops --- 菜单的高度数组:[56, 112, 168, 224, 280, 336, 392, 448, 504, 560, 616, 672, 728, 784, 840, 896, 952, 1008, ......]
const menuTops = []
let top2 = 0
goodsTops.push(top2)
// 找到所有分类的item
const items = this.$refs.siderMenu.getElementsByClassName('menu-item')
Array.prototype.slice.call(items).forEach(item => {
top2 = top2 + item.clientHeight
menuTops.push(top2)
})
this.menuTops = menuTops
}
},
// 点击左侧列表右侧滚动到相应位置
handleClickMenu (index) {
// 得到目标scrollY
const y = this.goodsTops[index]
// 立即更新scrollY
this.goodsScrollY = y
// 平滑滑动右侧列表
if (index === 0) {
this.goodsScroll.scrollTo(0, -y, 300)
} else {
this.goodsScroll.scrollTo(0, -(y + 10), 300)
}
},
- scss部分
pkg-content-detail__left {
flex: 1;
height: 100%;
overflow: hidden;
.sider-menu {
.menu-item {
position: relative;
display: flex;
align-items: center;
width: 100%;
height: 112px;
padding: 0 25px;
color: #666666;
font-weight: 400;
font-size: 32px;
background: #F7F7F7;
}
.active {
color: #318FF5;
font-weight: 600;
background: #fff;
}
.active + .menu-item {
border-radius: 0 12px 0 0;
}
}
}
pkg-content-detail__right {
flex: 3;
height: 100%;
padding: 0 14px;
overflow: hidden;
background: #fff;
.content {
padding-top: 10px;
}
&-item:not(:first-child) {
padding-top: 30px;
}
}