微信小程序之抖音微视无限滑动视频列表自定义组件编写
一.先上效果图
看到上面,你可能首先会想到,使用swiper然后将swiper的circular设置为true,那么,想象一下,假如视频很多的情况下,1千个,甚至上万个的情况下,只要有一个swiper-item,就会渲染一个Video播放器,这样是非常耗费性能的,本文将在这个基础上做优化,大致思路就是,不管我们的数据有多少条,我们都只渲染3个swiper-item,再配合swiper的切换,切换swiper-item的内容实现复用
二.swiper实现循环滑动
<view class="video-swiper-container">
<swiper class="video-swiper" circular vertical>
<swiper-item wx:for="{{_videoList}}" wx:key="*this">
<view class="video_item" style="background:{{index % 3 == 0 ? '#f00' : (index % 3 == 1 ? '#0f0':'#00f')}}">
{{item.index}}
</view>
</swiper-item>
</swiper>
</view>
videoList: {
type: Array,
value: [],
observer: function observer() {
let newVal = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
let _videoList = newVal.map((item, index) => {
let result = {
index: index,
url: item,
objectFit: 'contain'
}
return result;
})
this.setData({
_videoList: _videoList
})
}
}
代码很简单,只是将swiper的circular设置为了true,并且指定滑动方向为垂直方向,至于video标签,咱们先用一个View标签替换
三.第一次初始化
如果数据本身就小于3条,那么直接把传入的数据全部渲染,swiper默认选中第一个即可,如果数据大于3条,需要根据传入的index确定第一次加载时,swiper-item需要渲染的三个数据项,swiper默认选中第二个
- 如果传入的index是0,那么,swiper依次渲染最后一条,第一条,第二条
- 如果传入的index是最后一个,那么依次渲染倒数第二条,最后一条,第一条
- 如果传入的index是其他位置的,则依次渲染上一条,当前index,下一条
<view class="video-swiper-container">
<swiper class="video-swiper" circular vertical current="{{_videoList.length >= 3 ? 1:playIndex}}">
<swiper-item wx:for="{{curQueue}}" wx:key="*this">
<view class="video_item" style="background:{{index % 3 == 0 ? '#f00' : (index % 3 == 1 ? '#0f0':'#00f')}}">
{{item.index}}
</view>
</swiper-item>
</swiper>
</view>
videoList: {
type: Array,
value: [],
observer: function observer() {
let newVal = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
let _videoList = newVal.map((item, index) => {
let result = {
index: index,
url: item,
objectFit: 'contain'
}
return result;
})
this.setData({
_videoList: _videoList
})
this._videoListChange()
}
},
playIndex: {
type: Number,
value: 0
}
_videoListChange: function() {
let playIndex = this.data.playIndex;
let curQueue = this.data.curQueue;
let videoList = this.data._videoList;
if(curQueue.length <= 0) {
if (videoList.length >= 3) {
if (playIndex == 0) {
curQueue[0] = videoList[videoList.length - 1]
curQueue[1] = videoList[0]
curQueue[2] = videoList[1]
} else if (playIndex == videoList.length - 1) {
curQueue[0] = videoList[videoList.length - 2]
curQueue[1] = videoList[videoList.length - 1]
curQueue[2] = videoList[0]
} else {
curQueue[0] = videoList[playIndex - 1]
curQueue[1] = videoList[playIndex]
curQueue[2] = videoList[playIndex + 1]
}
} else {
curQueue = videoList;
}
this.setData({
curQueue: curQueue
})
}
}
四.配合swiper的切换,切换swiper-item的内容实现复用
当swiper切换完成后,判断是向上滑动切换到下一个,还是向下滑动切换上一个,假如总共有5条数据
- 向上滑动切换下一个
一开始,第二个swiper-item被选中,实际显示的是第一条数据
第一次向上滑动切换后,此时会切换到第三个swiper-item,实际显示的是第二条数据,只需要将第一个swiper-item显示的内容设置成第三条数据;
再一次向上滑动切换后,此时会切换到第一个swiper-item,实际显示的是第三条数据,只需要将第二个swiper-item显示的内容设置成第四条数据;
再一次向上滑动切换后,此时会切换到第二个swiper-item,实际显示的是第四条数据,只需要将第三个swiper-item显示的内容设置成第五条数据;
再一次向上滑动切换后,此时会切换到第三个swiper-item,实际显示的是第五条数据,只需要将第一个swiper-item显示的内容设置成第一条数据;
…
- 向下滑动切换上一个
一开始,第二个swiper-item被选中,实际显示的是第一条数据
第一次向下滑动切换后,此时会切换到第一个swiper-item,实际显示的是第五条数据,只需要将第三个swiper-item显示的内容设置成第四条数据;
再一次向下滑动切换后,此时会切换到第三个swiper-item,实际显示的是第四条数据,只需要将第二个swiper-item显示的内容设置成第三条数据;
再一次向下滑动切换后,此时会切换到第二个swiper-item,实际显示的是第三条数据,只需要将第一个swiper-item显示的内容设置成第二条数据;
再一次向下滑动切换后,此时会切换到第一个swiper-item,实际显示的是第二条数据,只需要将第三个swiper-item显示的内容设置成第一条数据;
…
<view class="video-swiper-container">
<swiper class="video-swiper" circular vertical current="{{_videoList.length >= 3 ? 1:playIndex}}" bindchange="onSwiperChange">
<swiper-item wx:for="{{curQueue}}" wx:key="*this">
<view class="video_item" style="background:{{index % 3 == 0 ? '#f00' : (index % 3 == 1 ? '#0f0':'#00f')}}">
{{item.index}}
</view>
</swiper-item>
</swiper>
</view>
data: {
curQueue:[],
_last: -1,
},
onSwiperChange: function (event) {
let _data = this.data;
let _last = _data._last;
let curQueue = _data.curQueue;
let current = event.detail.current;
let videoList = _data._videoList;
if (videoList.length >= 3) {
let diff = current - _last;
if (diff === 0) return;
this.data._last = current;
let direction = diff === 1 || diff === -2 ? 'up' : 'down';
/
let curItem = curQueue[current]
let realIndex = curItem.index
if (direction == 'up') {
let change = (current + 1) % 3
let next = (realIndex + 1) % videoList.length
curQueue[change] = videoList[next]
} else {
let change = (current - 1) < 0 ? 2 : current - 1
let pre = (realIndex - 1) < 0 ? (videoList.length - 1) : (realIndex - 1)
curQueue[change] = videoList[pre]
}
this.setData({
curQueue: curQueue
})
}
}
五.最后把View换成Video,并控制播放即可
<view class="video-swiper-container">
<swiper class="video-swiper" circular vertical current="{{_videoList.length >= 3 ? 1:playIndex}}" bindchange="onSwiperChange">
<swiper-item wx:for="{{curQueue}}" wx:key="*this">
<view class="video_item" style="background:{{index % 3 == 0 ? '#f00' : (index % 3 == 1 ? '#0f0':'#00f')}}">
<video
style="width: 100%; height: 100%;"
id="video_{{index}}"
loop="{{loop}}"
enable-play-gesture
enable-progress-gesture
show-center-play-btn="{{false}}"
direction="0" controls="{{true}}"
src="{{item.url}}"
object-fit="{{item.objectFit || 'cover'}}"/>
</view>
</swiper-item>
</swiper>
</view>
Component({
/**
* 组件的属性列表
*/
properties: {
videoList: {
type: Array,
value: [],
observer: function observer() {
let newVal = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
let _videoList = newVal.map((item, index) => {
let result = {
index: index,
url: item,
objectFit: 'contain'
}
return result;
})
this.setData({
_videoList: _videoList
})
this._videoListChange()
}
},
playIndex: {
type: Number,
value: 0
},
},
lifetimes: {
attached: function attached() {
this.data._videoContexts = [wx.createVideoContext('video_0', this), wx.createVideoContext('video_1', this), wx.createVideoContext('video_2', this)];
}
},
/**
* 组件的初始数据
*/
data: {
curQueue:[],
_last: 1,
_videoContexts: []
},
/**
* 组件的方法列表
*/
methods: {
_videoListChange: function() {
let playIndex = this.data.playIndex;
let curQueue = this.data.curQueue;
let videoList = this.data._videoList;
if(curQueue.length <= 0) {
if (videoList.length >= 3) {
if (playIndex == 0) {
curQueue[0] = videoList[videoList.length - 1]
curQueue[1] = videoList[0]
curQueue[2] = videoList[1]
} else if (playIndex == videoList.length - 1) {
curQueue[0] = videoList[videoList.length - 2]
curQueue[1] = videoList[videoList.length - 1]
curQueue[2] = videoList[0]
} else {
curQueue[0] = videoList[playIndex - 1]
curQueue[1] = videoList[playIndex]
curQueue[2] = videoList[playIndex + 1]
}
} else {
curQueue = videoList;
}
this.setData({
curQueue: curQueue
}, () => {
this.playCurrent(videoList.length >= 3 ? 1:playIndex);
})
}
},
onSwiperChange: function (event) {
let _data = this.data;
let _last = _data._last;
let curQueue = _data.curQueue;
let current = event.detail.current;
let videoList = _data._videoList;
if (videoList.length >= 3) {
let diff = current - _last;
if (diff === 0) return;
this.data._last = current;
let direction = diff === 1 || diff === -2 ? 'up' : 'down';
/
let curItem = curQueue[current]
let realIndex = curItem.index
if (direction == 'up') {
let change = (current + 1) % 3
let next = (realIndex + 1) % videoList.length
curQueue[change] = videoList[next]
} else {
let change = (current - 1) < 0 ? 2 : current - 1
let pre = (realIndex - 1) < 0 ? (videoList.length - 1) : (realIndex - 1)
curQueue[change] = videoList[pre]
}
this.setData({
curQueue: curQueue
}, () => {
this.playCurrent(current);
})
}
},
playCurrent: function playCurrent(current) {
let curQueue = this.data.curQueue
this.data._videoContexts.forEach((ctx, index) => {
if(index !== current) {
ctx.pause()
ctx.seek(0)
} else {
ctx.play()
}
});
},
}
})