最终效果
由于很多人不知道看一看还有个视频功能,所以这里先让大家看下我们最终要完成的效果。
它的入口在 发现 -> 看一看 -> 精选 -> 随便找个视频点进去即可。
初步想法
由效果可以看出,其实就是需要监听视频的滚动,当超出可视区范围多少px,就切换到下一个视频。
要实现这个功能,大多数人的想法都是:监听scroll 事件后,在调用目标元素的getBoundingClientRect()方法,得到它对应于视图的坐标,再判断是否在可视区域之内,然后切换视频。
缺点
但是这样做的缺点是:调用目标元素的getBoundingClientRect 是会触发重排的,尤其是元素一多起来,调用所有元素的getBoundingClientRect得到信息在进行判断所以很容易造成性能问题。而且这种切换计算的逻辑会写的非常复杂,可以自行脑补一下。
所以我们要换个思维,不要通过监听scoll事件去计算目标元素距离顶部或者底部距离。而应该是直接监听当前目标元素是否还在可视区域内。当离开可视区域的时候,切换到下一个。
整理完大致思路之后,终于要开搞了。
实现
前面已经分析了要通过监听当前目标元素是否还在可视区域内来做切换的动作,那么有什么API是可以用来做这件事的呢?
这个API是用来观察目标元素与指定元素交集的变化。当交集 < 0 的时候,说明不在指定元素区域内。当目标元素进入或者退出指定元素的时候,会执行相应的回调函数。所以我们可以通过这个API注册一个回调函数用于切换视频。
在小程序里,同样提供了这个API,是IntersectionObserver。
有了这个API,就可以开始干活了。由于我们这个区域是一个滚动区域,所以我用了scoll-view。
index.wxml 文件
<scroll-view>
<view wx:for="{{ videos }}" wx:for-index="idx" wx:for-item="videoItem">
<!-- <view class="{{ currentPlayVideoIndex === idx ? 'active test' : 'test'}}" data-index="{{ idx }}" id="{{ videoItem.video_id }}"> {{ idx }}dddd</view> -->
<span class="{{ currentPlayVideoIndex === idx ? 'active' : ''}}">{{ idx }}ddddddd </span>
<video
id="{{ videoItem.video_id }}"
data-index="{{ idx }}"
preload
src="http://wxsnsdy.tc.qq.com/105/20210/snsdyvideodownload?filekey=30280201010421301f0201690402534804102ca905ce620b1241b726bc41dcff44e00204012882540400&bizid=1023&hy=SH&fileparam=302c020101042530230204136ffd93020457e3c4ff02024ef202031e8d7f02030f42400204045a320a0201000400"
class='video-item'
muted
controls>
</video>
</view>
</scroll-view>
index.wxss
.video-item {
height: 450px;
}
.test {
width: 100%;
height: 450px;
border: 1px solid red;
padding: 30px;
}
.active {
color: pink;
}
Page({
/**
* 页面的初始数据
*/
data: {
videos: [{
video_id: 'mpVideo0',
url: 'http://wxsnsdy.tc.qq.com/105/20210/snsdyvideodownload?filekey=30280201010421301f0201690402534804102ca905ce620b1241b726bc41dcff44e00204012882540400&bizid=1023&hy=SH&fileparam=302c020101042530230204136ffd93020457e3c4ff02024ef202031e8d7f02030f42400204045a320a0201000400',
}, {
video_id: 'mpVideo1',
url: 'http://mpvideo.qpic.cn/tjg_2394158861_50000_01730a9db3924ffa98201662d51615ed.f10002.mp4?dis_k=23639703f249e3c59cf674369cfcac86&dis_t=1562297977',
}, {
video_id: 'mpVideo2',
url: 'http://mpvideo.qpic.cn/tjg_2394158861_50000_01730a9db3924ffa98201662d51615ed.f10002.mp4?dis_k=23639703f249e3c59cf674369cfcac86&dis_t=1562297977',
}, {
video_id: 'mpVideo3',
url: 'http://mpvideo.qpic.cn/tjg_2394158861_50000_01730a9db3924ffa98201662d51615ed.f10002.mp4?dis_k=23639703f249e3c59cf674369cfcac86&dis_t=1562297977',
}, {
video_id: 'mpVideo4',
url: 'http://mpvideo.qpic.cn/tjg_2394158861_50000_01730a9db3924ffa98201662d51615ed.f10002.mp4?dis_k=23639703f249e3c59cf674369cfcac86&dis_t=1562297977',
}, {
video_id: 'mpVideo5',
url: 'http://mpvideo.qpic.cn/tjg_2394158861_50000_01730a9db3924ffa98201662d51615ed.f10002.mp4?dis_k=23639703f249e3c59cf674369cfcac86&dis_t=1562297977',
}, {
video_id: 'mpVideo6',
url: 'http://mpvideo.qpic.cn/tjg_2394158861_50000_01730a9db3924ffa98201662d51615ed.f10002.mp4?dis_k=23639703f249e3c59cf674369cfcac86&dis_t=1562297977',
}, {
video_id: 'mpVideo7',
url: 'http://mpvideo.qpic.cn/tjg_2394158861_50000_01730a9db3924ffa98201662d51615ed.f10002.mp4?dis_k=23639703f249e3c59cf674369cfcac86&dis_t=1562297977',
}],
currentPlayVideoIndex: 0,
isActive: true
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
// onLoad 的时候立刻调用handleVideoScroll
// 对视频进行监听
this.handleVideoScroll();
// cgi请求,用于获取videos 的数据,由于是demo演示,我直接写死了videos...
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {
},
controlVideos: function (res) {
console.log('调用controvideos', res);
},
handleVideoScroll: function () {
const currentId = this.data.videos[this.data.currentPlayVideoIndex].video_id;
// 关键代码
// relativeToViewport 这里指定对比的就是viewport,viewport的意思就是document中的可视区域
this.observerObj = wx.createIntersectionObserver().relativeToViewport();
console.log('listen ' + currentId);
// 监听目标视频跟viewport相交区域的变化
this.observerObj.observe(`#${currentId}`, this.controlVideos);
}
})
copy上面的代码进入小程序,你就会看到这样一个界面。
其中外面的一圈表示的是viewPort,里面一层就是我们现在正在监听的视频,我用右上角的粉色字体来标记了,它的回调函数是controlVideos。当目标视频进入或者退出viewport的时候,controlVideos就会执行。
onLoad的时候执行了handleVideoScroll,这时候开始对目标视频进行监听,此时目标元素在viewport内,所以会调用controVideos,打印出相关信息。
其他的字段先不说,其中的intersectionRatio表示了他们相交的比例。其中1表示完全在viewport内,0表示不在viewport内。
如果我持续去滚动第一个视频,直到它看不到了,就会看到控制台打印出
这时候的intersectionRatio = 0,代表已经不在viewport内了,所以我们就可以将currentPlayIndex 切换到下一个了。
切换代码如下:
controlVideos: function (res) {
const { currentPlayVideoIndex } = this.data;
console.log('当前currentIndex', currentPlayVideoIndex)
const currentId = this.data.videos[currentPlayVideoIndex].video_id;
if (res && res.intersectionRatio > 0) {
// 视频在可视区域内,播放视频
wx.createVideoContext(currentId).play();
console.log("play" + currentPlayVideoIndex)
} else {
// 需要切换视频的时候,将当前视频暂停播放
// 并且通过handleVideoScroll 来播放下一个视频
wx.createVideoContext(currentId).pause();
// 切换到下一个视频
this.setData({ 'currentPlayVideoIndex': currentPlayVideoIndex + 1 }, () => {
// 注意切换完成之后,还需要在调用handleVideoScroll 来对下一个视频进行绑定
this.handleVideoScroll();
});
}
},
到这一步,应该就可以看到这样的向下切换的效果了。
但是,我们现在只是做下向下滚动的切换。那么向上的呢?要做向上滑动的切换,首先要知道视频是在向下还是向上滑动。这里有个字段可以帮助我们识别:boundingClientRect 。
它表示的是目标元素相对与viewport的节点信息。当视频向上滚动的时候,它距离viewport的top值为负,向下滚动的时候,为正值。
有了这个字段,我们就可以通过判断向上还是向下的滚动,来切换视频了。
controlVideos: function (res) {
const { currentPlayVideoIndex } = this.data;
console.log('当前currentIndex', currentPlayVideoIndex)
const currentId = this.data.videos[currentPlayVideoIndex].video_id;
if (res && res.intersectionRatio > 0) {
// 视频在可视区域内,播放视频
wx.createVideoContext(currentId).play();
console.log("play" + currentPlayVideoIndex)
} else {
// 需要切换视频的时候,将当前视频暂停播放,并且通过handleVideoScroll 来播放下一个视频
wx.createVideoContext(currentId).pause();
// 当top < 0的时候,说明是在向上滑动,这时候currentPlayVideoIndex 需要加1
if (res.boundingClientRect.top < 0) {
if (currentPlayVideoIndex < this.data.videos.length - 1) {
this.setData({ 'currentPlayVideoIndex': currentPlayVideoIndex + 1 }, () => {
// 同时解绑第一个视频,保证同一个时间只监听一个视频
this.observerObj.disconnect();
this.handleVideoScroll();
});
}
} else {
// 当top > 0的时候,说明是在向下滑动,这时候currentPlayVideoIndex 需要减1
if (currentPlayVideoIndex - 1 < 0) {
return;
}
this.setData({ 'currentPlayVideoIndex': this.data.currentPlayVideoIndex - 1 }, () => {
this.observerObj.disconnect();
this.handleVideoScroll();
})
}
},
但是我们的产品在体验的过程中,会提出并不是完全看不见了才去切换,可能想要还剩个150px就切换了,所以我这里要对viewport调整一下
this.observerObj = wx.createIntersectionObserver().relativeToViewport({ top: -300, bottom: -300 });
完成之后,你就可以缓缓的滑动你的视频,实现视频切换的效果了。可以看到当视频差不多被遮住不到一半,就开始切换了。
总结
整个过程其实就是好好利用了IntersectionObserver这个API而已。当然现在只是一个非常简单的实现,性能问题,以及快读滑动的情况都无法应对,我们下一篇在接着~。
支持,等待更新。如果能顺便上个代码片段给懒人们就好了😂
快速滚白屏能解决?
性能方面是怎么考虑的呢?比如:onLoad() 的时候加载所有的视频进行渲染;还是分页进行渲染呢?分页进行渲染,后续是怎么给它喂数据的呢
老铁,有在真机上试过么?
老铁 下一篇呢
图应该是好看的吧
求整个 wxml wxjs wxcs 代码
蹲
插眼