移动端中需要使用swiper插件实现tab切换和手势滑动的,在各种APP上我们可以很常见,但在小程序上实现这个看起来有点难,因为swiper插件滑动到下一屏的时候位置总是会回到跟上一屏相同的位置。我记得微信刚出来不久,有一个这样子的需求,那时候是另外一个同事在跟,跟我说到过这个功能无法实现。最近我也接到这个需求,于是我认真的看了一下微信组件文档,发现swiper+scroll-view组件结合是可以实现这个功能。
需要说的是,我的每个swiper-item数据不是固定的,每个swiper-item列表数据都有滚动底部会无线加载,所以说我无法在一开始就确定了所有的子项的高度,另外每个swiper-item都需要滚动,这就是说它们的滚动到的位置每个都不是一样的。每次滑动到下一屏的时候,要确定最外层的scroll-view的scroll-top值。
由于swiper组件并不是自适应高度的,而我们每个swiper-item的高度并不是一样的,所以第一步就是计算每个swiper-item的高度,并赋值给swiper组件。xml代码如下:
代码语言:javascript
复制
<swiper current="{{current}}" style="height:{{list[curListId].swiperHeight}}rpx;" class="swiper-box" duration="300" bindchange="bindchange"> <block wx:for="{{nav}}"> <swiper-item> <block wx:if="{{list[item.columnId].data.length>0}}"> <view class="list"> <block wx:for="{{list[item.columnId].data}}" wx:for-item="art"> <view class="item"> <navigator url="../art/art?id={{art.id}}" hover-class="none" open-type="navigate"> <view class="img"> <image mode="" src="{{art.imageUrl}}"></image> <view class="meta"> <view class="avatar"> <image src="{{art.userImage}}"></image> </view> <view class="nickName"> <text>{{art.userName}}</text> </view> </view> </view> <view class="title">{{art.title}}</view> <view class="des">{{art.summary}}</view> </navigator> </view> </block> </view> </block> </swiper-item> </block></swiper>
可以看到我里面定义了一个循环,把所有子项的数据都放在list里面。当然前提我们已经知道要判断的list[item.columnId]是否已经存在值,如果没有我们需要先请求数据。而每个对象对应的下标我们已经知道,所以我们一开始进来就确认了所有swiper-item要加载的内容,后续滑动加载内容时如果没有,我们就会去请求,如果有则使用,并且滑动到底部时,我们根据curListId知道当前需要请求那个对象。把剩下的数据合并在同一个对象里面。
计算swiper-item的高度,可以使用微信提供的一个 api createSelectorQuery,我这里没用,因为我的列表每一个字内容都是固定好高度的。所以我只要知道获取的数据数组的长度是多少,然后计算每个子项的高度,就能得到swiper-item的高度了。
记录滚动值,这个很简单,因为我们的页面最外层就是一个scroll-view组件,所以我们只要给这个scroll-view一个bindscroll事件,在滚动的过程中,不断的记录更新每一个子项它最后滚动到的位置,下次进入这一屏,就看看数据里面有没有这个滚动值,没有的话,就是第一次进入,默认为0,如果有值,说明之前我们已经滚动过一次,则赋值给scroll-view的scroll-top。
代码语言:javascript
复制
scroll: function(e) { var self = this; setTimeout(function() { console.log(e.detail.scrollTop); var list = self.data.list; if (list[self.data.curListId]) { list[self.data.curListId].scrollTop = e.detail.scrollTop; self.setData({ list }) } }, 300);},
就是实现这种常见的资讯tab列表,需要滑动加载数据,切换自动回到上次滚动的位置。
JS:
代码语言:javascript
复制
// pages/live/live.jsconst app = getApp()const config = require('../../pc.config.js');Page({ /** * 页面的初始数据 */ data: { nav: [{ "columnName": '全部', "columnId": "000081525" }, { "columnName": '好物种草', "columnId": "000084624" }, { "columnName": '收纳支招', "columnId": "000084644" }, { "columnName": '细节控', "columnId": "000084645" }, { "columnName": '小改造', "columnId": "000084625" }, { "columnName": '洗刷刷', "columnId": "000084626" }, { "columnName": '睡眠研究', "columnId": "000084646" }], navPosition: [], list: {}, curListId: 0, endTipHidden: false, endTip: '正在加载', current: 0, scrollLeft: 0, px2rpx: 2, winWidth: 375, scrollTop: 0 }, /** * 生命周期函数--监听页面加载 */ onLoad: function(options) { var index = options.index || 0; this.getSystem(); this.getNav(index); }, timer: null, getSystem: function() { var self = this; wx.getSystemInfo({ success: function(res) { console.log(res); self.setData({ winWidth: res.windowWidth, px2rpx: 750 / res.windowWidth }) }, }); }, //获取导航和节点 getNav: function(index) { var self = this; wx.request({ url: config.getAPI('liveNav'), success: function(res) { // console.log(res); self.setData({ nav: res.data.livingColumn, current: index }); //获取导航的初始位置 const query = wx.createSelectorQuery() query.selectAll('.toc').boundingClientRect(); // query.selectViewport().scrollOffset() query.exec(function(res) { console.log(res); console.log(res[index]); self.setData({ navPosition: res[0] }) if (index >= 4) { self.setData({ scrollLeft: res[0][index].left }) } }) self.getList(res.data.livingColumn[index].columnId); } }) }, //滑到底部加载更多 loadMoreList: function() { var list = this.data.list; var cid = this.data.curListId; if (list[cid].pageNo < list[cid].pageCount) { this.getList(cid); } }, //请求列表 getList: function(cid) { var self = this; this.setData({ curListId: cid }) var list = this.data.list; if (!list[cid]) { wx.request({ url: config.getAPI('liveList') + `?columnType=livingColumn&columnId=${cid}&pageSize=10&pageNo=1`, success: function(res) { // console.log(res.data); var obj = {}; obj.pageNo = res.data.pageNo; obj.pageCount = Math.ceil(res.data.total / res.data.pageSize); obj.total = res.data.total; obj.data = res.data.data; obj.swiperHeight = res.data.total > res.data.pageSize * res.data.pageNo ? res.data.pageSize * res.data.pageNo * 518 + 102 : res.data.total * 518 + 102; if (res.data.pageNo * res.data.pageSize >= res.data.total) { obj.endTip = '没有更多了'; obj.endTipHidden = true; } else { obj.endTip = '正在加载'; } list[cid] = obj; self.setData({ list }) } }) } else { if (list[cid].pageNo < list[cid].pageCount) { wx.request({ url: config.getAPI('liveList') + `?columnType=livingColumn&columnId=${cid}&pageSize=10&pageNo=${list[cid].pageNo+1}`, success: function(res) { var obj = {}; obj.pageNo = res.data.pageNo; obj.pageCount = Math.ceil(res.data.total / res.data.pageSize); obj.total = res.data.total; obj.data = list[cid].data.concat(res.data.data); if (res.data.pageNo * res.data.pageSize >= res.data.total) { obj.endTip = '没有更多了'; obj.endTipHidden = true; } else { obj.endTip = '正在加载'; } obj.swiperHeight = res.data.total > res.data.pageSize * res.data.pageNo ? res.data.pageSize * res.data.pageNo * 518 + 102 : res.data.total * 518 + 102; list[cid] = obj; self.setData({ list }) } }) } } }, //切换导航(包含滑动swiper和切换导航跳转) switchNav: function(index) { if (index && this.data.current == index) return; console.log('switchtab'); var cid = this.data.nav[index].columnId; var self = this; var scrollLeft = 0; if (self.data.navPosition[index].right * self.data.px2rpx + 62 >= 750) { scrollLeft = self.data.navPosition[index].left; } var list = self.data.list; var scrollTop = 0; if (list[cid] && list[cid].scrollTop) { scrollTop = list[cid].scrollTop } if (!list[cid]) { clearTimeout(self.timer); self.timer = null; self.timer = setTimeout(function() { self.setData({ curListId: cid, current: index, scrollLeft, scrollTop }) self.getList(cid); }, 500); } else { self.setData({ curListId: cid, current: index, scrollLeft, scrollTop }) } }, //切换导航 changeTab: function(e) { console.log('changetab'); var index = e.currentTarget.dataset.index; this.switchNav(index); }, //滑动swiper bindchange: function(e) { console.log('changeswiper'); var index = e.detail.current; //加上这个避免swiper过程,swiper-item会发生滑动混乱,滑动过快就会一直在闪动,新的API属性,touch说明是用户接触滑动,而不是自动滑动 if (e.detail.source && e.detail.source =='touch'){ this.switchNav(index); } }, //滚动记录之前的滚动位置 scroll: function(e) { var self = this; setTimeout(function() { console.log(e.detail.scrollTop); var list = self.data.list; if (list[self.data.curListId]) { list[self.data.curListId].scrollTop = e.detail.scrollTop; self.setData({ list }) } }, 300); },})
xml
代码语言:javascript
复制
<!--pages/live/live.wxml--><scroll-view class="g-doc" scroll-y="true" bindscrolltolower="loadMoreList" scroll-top="{{scrollTop}}" bindscroll="scroll"> <view class="tab"> <view class="tab-inner"> <scroll-view class="scroll-bangdan" scroll-x="true" scroll-left="{{scrollLeft}}"> <view class="ctrl" style="width:1054rpx"> <block wx:for="{{nav}}"> <view class="toc{{current==index?' cur':''}}" bindtap="changeTab" data-index="{{index}}"> <view class="text"> <text>{{item.columnName}}</text> </view> </view> </block> </view> </scroll-view> <view class="mask"></view> </view> </view> <view class="tab-container"> <swiper current="{{current}}" style="height:{{list[curListId].swiperHeight}}rpx;" class="swiper-box" duration="300" bindchange="bindchange"> <block wx:for="{{nav}}"> <swiper-item> <block wx:if="{{list[item.columnId].data.length>0}}"> <view class="list"> <!-- <view class="nodata">这里空空如也</view> --> <block wx:for="{{list[item.columnId].data}}" wx:for-item="art"> <view class="item"> <navigator url="../art/art?id={{art.id}}" hover-class="none" open-type="navigate"> <view class="img"> <image mode="" src="{{art.imageUrl}}"></image> <view class="meta"> <view class="avatar"> <image src="{{art.userImage}}"></image> </view> <view class="nickName"> <text>{{art.userName}}</text> </view> </view> </view> <view class="title">{{art.title}}</view> <view class="des">{{art.summary}}</view> </navigator> </view> </block> </view> </block> </swiper-item> </block> </swiper> <view class="m-end" hidden="{{list[curListId].endTipHidden}}"> <block wx:if="{{endTip=='正在加载'}}"> <view class="icon"></view> </block> {{endTip}} </view> <view class="m-end" hidden="{{!list[curListId].endTipHidden}}">没有更多了</view> </view></scroll-view>
CSS
page { height: 100%; overflow: hidden;}.g-doc { height: 100%;}.tab { position: fixed; top: 0; left: 0; border-bottom: 1px solid #f8f8f8; background: #fff; z-index: 1; width: 100%; height: 100rpx; line-height: 100rpx;}.tab-inner{ height: 100rpx; overflow: hidden;}/* .tab-con{width: 100%;} */.scroll-bangdan { width: 100%; position: relative; height: 130rpx;}.tab .ctrl { display: -webkit-box; display: -moz-box; display: -ms-flexbox; display: -webkit-flex; display: flex; flex-flow: row wrap; -webkit-flex: row wrap; font-size: 28rpx; height: 100rpx; line-height: 100rpx;}.tab .ctrl .toc { text-align: center; margin-left: 50rpx; white-space: nowrap;}.tab .ctrl .toc:first-child { margin-left: 40rpx;}.tab .ctrl .toc:last-child { margin-right: 40rpx;}.tab .ctrl .toc .text { display: inline-block; position: relative;}.tab .ctrl .toc.cur .text::before { content: "\20"; display: block; position: absolute; height: 10rpx; background: #fbe251; left: 0; bottom: 30rpx; width: 100%; z-index: 0;}.tab .ctrl .toc .text text { position: relative; z-index: 2;}.tab .mask { background: url(https://www1.pchouse.com.cn/2018/weixinminipro/mask.png) no-repeat top right; background-size: 113rpx; width: 83rpx; height: 100rpx; position: fixed; right: 0; top: 0; z-index: 50;}.list { padding-top: 100rpx;}.list .item { padding: 30rpx 40rpx 40rpx;}.list .item .img { width: 670rpx; height: 336rpx; overflow: hidden; position: relative;}.list .item .meta { position: absolute; left: 0; bottom: 0; height: 84rpx; width: 100%; padding-top: 16rpx; line-height: 60rpx; color: #fff; font-size: 28rpx; overflow: hidden; background: url(https://www1.pchouse.com.cn/2018/weixinminipro/pic-modal.png?v2) repeat-x center bottom; background-size: auto 99rpx;}.list .item .meta .avatar { width: 60rpx; height: 60rpx; float: left; margin-left: 24rpx; margin-right: 24rpx;}.list .item .meta .avatar image { width: 60rpx; height: 60rpx; border-radius: 100%;}.list .item .meta .nickName { float: left;}.list .item .img image { width: 670rpx; height: 336rpx; border-radius: 16rpx;}.list .item .title { height: 73rpx; line-height: 73rpx; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 36rpx;}.list .item .des { height: 40rpx; overflow: hidden; font-size: 24rpx; color: #aaa; text-overflow: ellipsis; white-space: nowrap;} .m-end { padding: 20rpx 0 34rpx; line-height: 28rpx; }
来源:https://cloud.tencent.com/developer/article/1740566