最近需求是做一个类似外卖软件的商品列表,左边类型标题栏一个滚动区域右边商品栏一个滚动区域这样子,两边的滚动是互相响应的。
搜了一下github,小程序好像还没有人开源出这个轮子来,当然安卓,ios,js这样的也是有的。所以就写了个demo~
实现功能
右边商品栏滚动到不同区域时:
1,左边类型标题栏会自动切换选中,并且滚动到中间位置。
2,最上标题切换。
点击左边标题栏一项时:
1,右边商品栏滚动到响应区域,标题切换。
备注
1,这里我用了wepy框架。其实这根框架没有一点关系,只不过我懒惰费事改回原生而已。wepy对象就相当于原生的wx对象,然后repeat其实就是个循环,没其他了。
2,存在的问题还是有的,虽然处理了,但是还是不清楚为什么会这样。scroll-top属性是不会跟随scroll-view滚动的而改变的,所以要自己手动改变,例如本来默认是0的,当你滑动到最底的时候他的值还是0,所以我每次滚动前先将scroll-top设置成目前值+0.00001(先做一个超级不明显的改变),再做赋值,这样就不会出现不生效的情况,但是不支持同步赋值,例如我先设置为0再设置设置成1,也是不生效的,所以我这里用了setTimeout异步了一下,就生效了。
实现思路
- 其实就两个事件,一个右边商品栏的滚动事件,一个就是左边标题栏的每一项的点击事件
- 初始化拿到数据的时候计算高度,距离,添加到每一项。
- 用的是scroll-view组件 然后通过改变scroll-top属性来控制滚动区域滚动,至于为什么我没有用scroll-into-view,上面说到了目前值+0.00001,scroll-into-view传的是id,试过改成一个不存在的id没结果是不起作用的,所以我还是选择用scroll-top。
实现代码
data
data = {
goodsList: [
{
title: '类型1',
good: ['0-1', '0-2', '0-3']
},
{
title: '类型2',
good: ['1-1', '1-2', '1-3', '1-4']
},
{
title: '类型3',
good: ['2-1', '2-2', '2-3']
},
{
title: '类型4',
good: ['3-1', '3-2', '3-3']
},
{
title: '类型5',
good: ['4-1', '4-2', '4-3']
},
{
title: '类型6',
good: ['4-1', '4-2', '4-3']
},
{
title: '类型7',
good: ['4-1', '4-2', '4-3']
},
{
title: '类型8',
good: ['4-1', '4-2', '4-3']
},
{
title: '类型9',
good: ['4-1', '4-2', '4-3']
},
{
title: '类型9',
good: ['4-1', '4-2', '4-3']
},
{
title: '类型9',
good: ['4-1', '4-2', '4-3']
},
{
title: '类型9',
good: ['4-1', '4-2', '4-3']
},
{
title: '类型9',
good: ['4-1', '4-2', '4-3']
}
],
inScroll: 0, // 滚动子项id
scrollTop: 0.00001, // 商品栏离顶部距离
typeTop: 0.000001, // 类型导航栏离顶部距离
// 屏幕高度
windowHeight: 0, // 获得可用窗口高度
goodsType: null // 商品栏上面固定的标题
}复制代码
- methods
methods = {
/**
* 点击左边的标题栏的一项,把那一项的序号传进来就完事了
* @param index
*/
toScrollItem (index) {
this.scrollTop = this.scrollTop += 0.00001
setTimeout(() => {
this.scrollTop = this.goodsList[index].top.begin
this.$apply()
})
},
/**
* 这里监听商品栏滚动条
* @param detail
*/
handleScroll({detail}) {
// 每次滚动都需要循环匹配,找寻滚动条到达那个区域,匹配到了或者到最后了就停止,是真的蛋疼。
for (let i = 0; i < this.goodsList.length; i += 1) {
// 进入循环了,这个商品列表的数组是经过加工的,在初始化的时候计算了每个种类项区域(top)和左边标题(typeTop)离顶部距离。
let v = this.goodsList[i]
// 如果滚动条与顶部的距离 >= 这一项开头的与顶部的距离 < 这一项结尾与顶部的距离,那么现在正显示的就是这个区域了。
if (detail.scrollTop >= v.top.begin && detail.scrollTop < v.top.end) {
// 记录下现在显示的是第几个区域。
this.inScroll = i
// 位于商品栏区域上面的标题跟着变成现在显示的区域。
this.goodsType = v.title
// 左边标题栏也跟着滚动到,如果可以(看标题栏有多长囖),将对应的区域移到中间。
this.typeTop = v.typeTop
// 匹配到了,并且做完该做的东西就跳出循环,免得他继续匹配。
break
}
}
}
}复制代码
onLoad
async onLoad() {
// 制作顶部距离,盒子高度,子项滚动id属性。
let [top, typeTop] = [0, 0]
this.goodsList.map((v, i) => {
// 这个商品栏区域总高度,因为每个子项高度都是固定的,x长度就是他的总高度。
this.goodsList[i].height = (v.good.length * 200 + 30)
// 计算这个商品栏的开头和结尾离头部的距离,就是此区域的位置,用来判断目前显示区域的。
// top就是这个区域开头与滚动区顶部的位置
this.goodsList[i].top = {begin: top, end: top + (v.good.length * 200 + 30)}
// 计算此项商品标题在商品类型标题栏的高度
this.goodsList[i].typeTop = typeTop
// 计算之后这个top就自增一个上一个区域的总高度的单位
top += (v.good.length * 200 + 30)
// 左边标题栏也自增一个固定长度单位,可以改的 ,看样式定义多少
typeTop += 100
})
// 获取屏幕总高度
const {windowHeight} = await wepy.getSystemInfoSync()
this.windowHeight = windowHeight
//
this.goodsType = this.goodsList[0].title
}复制代码
view
<template>
<view class="goods" style="height: {{windowHeight}}px">
<!--种类栏-->
<scroll-view
scroll-y
class="goods_type_list"
scroll-with-animation
scroll-top="{{typeTop > (windowHeight/2) ? (typeTop - (windowHeight/2)) : 0}}"
>
<repeat for="{{goodsList}}">
<view class="goods_type_item {{inScroll === index ? 'active' : ''}}"
@tap="toScrollItem({{index}})">
{{item.title}}
</view>
</repeat>
</scroll-view>
<!--商品栏-->
<view class="goods_list">
<view class="goods_type_fixed goods_type_title">{{goodsType}}</view>
<scroll-view scroll-y
scroll-top="{{scrollTop}}"
scroll-with-animation
bindscroll="handleScroll">
<repeat for="{{goodsList}}">
<view class="scroll_item">
<view class="goods_type_title" wx:if="{{index !== 0}}">title:{{item.title}}</view>
<view class="goods_type_title" wx:else></view>
<repeat for="{{item.good}}">
<view class="goods_item">{{item}}</view>
</repeat>
</view>
</repeat>
</scroll-view>
</view>
</view>
</template>复制代码
style
<style>
.goods{
position: relative;
}
.goods_item{
background: #ffd900;
height: 200px;
}
.goods_type_item{
height: 70px;
}
.scroll_item:nth-last-child(1){
min-height: 100.1%;
}
scroll-view{
height: inherit;
}
.goods_type_item.active{
background: #ffd888;
}
.goods_type_title{
height: 30px;
line-height: 30px;
font-size: 17px;
background-color: white;
width: 100%;
}
.goods_type_list{
position: absolute;
width: 100px;
}
.goods_list{
margin-left: 100px;height: inherit;position: relative;
}
.goods_type_fixed{
position: absolute;top: 0;z-index: 1;
}
</style>复制代码