最近需求是做一个类似外卖软件的商品列表,左边类型标题栏一个滚动区域右边商品栏一个滚动区域这样子,两边的滚动是互相响应的。
搜了一下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