最近版本迭代中的需求里有这么一条:视频详情页面,页面上下滑实现视频上一个/下一个的切换
拿到这个需求,我的思路有大致以下几种方案实现:
1、使用scroll-view或者swiper实现切换
分析:由于页面有video,文档有明确提示勿在 scroll-view、swiper中使用video
结论:该方案显然不可行 ❌
2、使用刷新实现切换
分析:直接监听用户下拉动作和上拉触底操作,实现数据请求渲染即可
结论:该方案显然可行 ✅✅
3、监听触摸手势事件,确认滑动方向,实现切换
分析:直接在video上绑定监听触摸手势的touch事件,根据触摸点相对位移确定滑动方向,进而实现上下滑动时请求新数据渲染页面
结论:该方案原理上是可行的 ✅
根据上述分析,我想验证方案3的可行性,问了下需求的紧急程度和时间进度,心里做了个预估,时间方面允许,于是我便开始代码验证。下面只抽取该功能相关的代码进行展示。
anchorDetail.wxml文件:
<view class="myContainer">
<video id="myVideo" src="{{detail.video_url}}" controls="{{false}}" autoplay="{{true}}" loop="{{true}}" objectFit="fill" controls="{{false}}" show-play-btn="{{false}}" enable-progress-gesture="{{false}}" style="top:{{videoheight}}%;" binderror="error" bindtouchmove="handletouchmove" bindtouchstart="handletouchstart" bindtouchend="handletouchend">
</video>
</view>
anchorDetail.wxss文件:
page {
height: 100%;
background-color: rgba(0, 0, 0, 0);
}
.myContainer {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
background-color: rgba(0, 0, 0, 0);
}
video {
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0);
}
anchorDetail.js文件:
//绑定事件---滑动
handletouchmove: function (event) {
console.log("handletouchmove: ", event)
if (this.data.flag !== 0) {
return
}
let currentX = event.touches[0].pageX;
let currentY = event.touches[0].pageY;
let tx = currentX - this.data.lastX;
let ty = currentY - this.data.lastY;
let text = "";
//左右方向滑动
if (Math.abs(tx) > Math.abs(ty)) {
if (tx < 0) {
text = "向左滑动";
this.data.flag = 1;
}else if (tx > 0) {
text = "向右滑动";
this.data.flag = 2
}
}
//上下方向滑动
else {
if (ty < 0) {
text = "向上滑动";
this.data.flag = 3
}else if (ty > 0) {
text = "向下滑动";
this.data.flag = 4
}
}
console.log(text);
switch (text) {
case '向上滑动':
this.upSlider();
break;
case '向下滑动':
this.downSlider();
break;
case '向左滑动':
break;
case '向右滑动':
break;
}
//将当前坐标进行保存以进行下一次计算
this.data.lastX = currentX;
this.data.lastY = currentY;
this.setData({
text: text
});
},
//绑定事件---开始滑动
handletouchstart: function (event) {
console.log("handletouchstart: ", event)
this.data.lastX = event.touches[0].pageX;
this.data.lastY = event.touches[0].pageY;
},
//绑定事件---滑动完毕
handletouchend: function (event) {
console.log("handletouchend: ", event)
this.data.flag = 0
this.setData({
text: "没有滑动",
});
},
//上滑
upSlider: function () {
var that = this;
var sl = setInterval(function () {
if (that.data.videoheight > -100) {
that.setData({
videoheight: that.data.videoheight - 2
});
} else {
that.requestSliderData(true)
clearInterval(sl);
}
}, 1);
},
//下滑
downSlider: function () {
var that = this;
var sl = setInterval(function () {
if (that.data.videoheight < 100) {
that.setData({
videoheight: that.data.videoheight + 2
});
} else {
that.requestSliderData(false)
clearInterval(sl);
}
}, 1);
}
//请求上/下一个数据
requestSliderData: function (isDownSlider) {
wx.showLoading({
title: '载入中...',
})
var that = this
var url = (isDownSlider ? 'anchor/next' : 'anchor/prev')
common.http(url, null, function (res) {
that.setData({
videoheight: 0
})
if (res.data.ok == "1") {
wx.hideLoading()
that.setData({
anchor_id: res.data.data.info.id,
detail: res.data.data.info,
hasMore: res.data.data.has_more,
share: res.data.data.info.share_info
})
} else {
if (res.data.ok == "168") {
that.setData({
hasMore: 0
})
}
wx.showToast({
title: res.data.msg,
icon: 'none',
duration: 2000
})
}
}, function (res) {
that.setData({
videoheight: 0
})
wx.showToast({
title: res.data.msg,
icon: 'none',
duration: 2000
})
})
},
模拟器运行,效果如预期一样。换成真机预览,触摸时竟然无效,what's wrong?
开启调试窗口打印,当手指在video上滑动时,压根就没触发touch事件
回头看文档,并没发现文档有说明video不响应touch事件,到小程序社区搜索,也有和我一样踩坑的,video原生组件不支持touch事件。
我尝试将触摸事件放到最外层的class="myContainer"的view上处理,模拟器正常,但到了真机上也是不触发touch事件。于是尝试使用cover-view,放置和video同层级绑定touch事件。
anchorDetail.wxml文件:
<view class="myContainer">
<!-- 由于真机上video不响应touch事件,故放置透明的cover-view处理touch事件 -->
<cover-view data-id="outCoverView" class="outCoverView" bindtouchmove="handletouchmove" bindtouchstart="handletouchstart" bindtouchend="handletouchend"></cover-view>
<video id="myVideo" src="{{detail.video_url}}" controls="{{false}}" autoplay="{{true}}" loop="{{true}}" objectFit="fill" controls="{{false}}" show-play-btn="{{false}}" enable-progress-gesture="{{false}}" style="top:{{videoheight}}%;" binderror="error">
<template is="anchorDetail" data="{{...detail,isConnection,userInfo,isIOS,onlookers}}"></template>
</video>
</view>
anchorDetail.wxss文件新增的cover-view样式:
/*宽高没有设置100%,因为会盖住其他按钮的tap事件*/
.outCoverView {
position: fixed;
width: 644rpx;
height: 1080rpx;
background-color: red;
top: 128rpx;
left: 0;
}
模拟器运行,发现不触发touch事件,并且控制台日志中有警告:VM5118:2 '<cover-view />' 暂不支持 'bindtouchstart' 事件。换真机运行时,控制台有打印触发了touch事件的。再看看安卓机预览效果,cover-view的touch事件没响应。猜想这个就算实现,应该也会引起问题,毕竟官方开发工具存在警告信息。另外,安卓手机上预览时发现无法触发触摸手势事件。
接下来继续找问题找解决方案吧~
我尝试用画布canvas处理touch事件,
anchorDetail.wxml文件:
<view class="myContainer">
<video id="myVideo" src="{{detail.video_url}}" controls="{{false}}" autoplay="{{true}}" loop="{{true}}" objectFit="fill" controls="{{false}}" show-play-btn="{{false}}" enable-progress-gesture="{{false}}" style="top:{{videoheight}}%;" binderror="error" bindtouchmove="handletouchmove" bindtouchstart="handletouchstart" bindtouchend="handletouchend">
</video>
<cover-view data-id="outCoverView" class="outCoverView" bindtouchmove="handletouchmove" bindtouchstart="handletouchstart" bindtouchend="handletouchend"></cover-view>
</view>
anchorDetail.js文件:由于canvas响应触摸事件时的event.touches是坐标点x和y,即需做如下修改。
//绑定事件---滑动
handletouchmove: function (event) {
console.log("handletouchmove: ", event)
if (this.data.flag !== 0) {
return
}
let currentX = event.touches[0].pageX;
let currentY = event.touches[0].pageY;
let tx = currentX - this.data.lastX;
let ty = currentY - this.data.lastY;
let text = "";
//左右方向滑动
if (Math.abs(tx) > Math.abs(ty)) {
if (tx < 0) {
text = "向左滑动";
this.data.flag = 1;
}else if (tx > 0) {
text = "向右滑动";
this.data.flag = 2
}
}
//上下方向滑动
else {
if (ty < 0) {
text = "向上滑动";
this.data.flag = 3
}else if (ty > 0) {
text = "向下滑动";
this.data.flag = 4
}
}
console.log(text);
switch (text) {
case '向上滑动':
this.upSlider();
break;
case '向下滑动':
this.downSlider();
break;
case '向左滑动':
break;
case '向右滑动':
break;
}
//将当前坐标进行保存以进行下一次计算
this.data.lastX = currentX;
this.data.lastY = currentY;
this.setData({
text: text
});
},
//绑定事件---开始滑动
handletouchstart: function (event) {
console.log("handletouchstart: ", event)
this.data.lastX = event.touches[0].pageX;
this.data.lastY = event.touches[0].pageY;
}
采用canvas处理触摸事件时,真机iOS是可行,安卓运行也OK。但有个问题就是:官方文档特意强调过的一点:canvas避免设置过大的宽高,在安卓下会有crash的问题。我测试过多次,真的应验了官方提醒。
再回到项目需求,如果妥协折衷的话,可以采取这种方案,针对安卓设置较小的宽高来处理也是可以的。PS: canvas的默认属性:
canvas {
width:300px;
height:150px;
display:block;
position:relative;
}
但考虑到实际用户群,安卓用户还是居多,而且canvas设置较小的宽高,会直接导致触摸区域变小,只能在有效区域滑动才能实现视频切换,用户体验也不佳。所以,综合考虑各种因素,决定换回方案2刷新方式实现,以保证用户体验性良好。先来看下官网介绍吧:
页面相关事件处理函数
-
onPullDownRefresh
: 下拉刷新- 监听用户下拉刷新事件。
- 需要在
app.json
的window
选项中或页面配置中开启enablePullDownRefresh
。 - 当处理完数据刷新后,
wx.stopPullDownRefresh
可以停止当前页面的下拉刷新。
-
onReachBottom
: 上拉触底
anchorDetail.wxml文件:
{
"enablePullDownRefresh": true,
"onReachBottomDistance": 1
}
注意:
1、enablePullDownRefresh默认是false,设置为true后页面才有下拉刷新功能;
2、onReachBottomDistance默认是50px,是设置页面上拉触底事件触发时距页面底部距离,这里我设置为1
anchorDetail.wxss文件:
page {
height: 104%;
background-color: rgba(0, 0, 0, 0);
}
cover-view {
letter-spacing: 1rpx;
line-height: 1.5;
}
.myContainer {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
background-color: rgba(0, 0, 0, 0);
}
video {
width: 100%;
height: 104%;
background-color: rgba(0, 0, 0, 0);
}
这里解释下:之所以设置页面page和video高度104%,而不是和myContainer一致为100%,是为了保证页面内容超出一屏可以上拉触底。因为如果页面内容不够高(超出一屏),是不可能出现上拉触底的情况的。
anchorDetail.js文件:
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function () {
console.log("onPullDownRefresh")
this.requestSliderData(false)
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {
console.log("onReachBottom")
this.requestSliderData(true)
}
//请求上/下一个数据
requestSliderData: function (isDownSlider) {
wx.showLoading({
title: '载入中...',
})
var that = this
var url = (isDownSlider ? 'anchor/next' : 'anchor/prev')
common.http(url, null, function (res) {
that.setData({
videoheight: 0,
canClickNextBtn: true
})
if (res.data.ok == "1") {
wx.hideLoading()
that.setData({
anchor_id: res.data.data.info.id,
detail: res.data.data.info,
hasMore: res.data.data.has_more,
share: res.data.data.info.share_info
})
} else {
if (res.data.ok == "168") {
that.setData({
hasMore: 0
})
}
wx.showToast({
title: res.data.msg,
icon: 'none',
duration: 2000
})
}
}, function (res) {
that.setData({
videoheight: 0,
canClickNextBtn: true
})
wx.showToast({
title: res.data.msg,
icon: 'none',
duration: 2000
})
}, function (res) {
if (isDownSlider) {
wx.pageScrollTo({
scrollTop: 0,
duration: 100
})
}else {
wx.stopPullDownRefresh()
}
})
}
至此,效果实现,但调试控制台打印发现,iOS上下拉刷新会触发上拉触底,导致请求下一个视频后立马有在请求上一个,会出现2次页面渲染后回到下拉刷新前的原始视频。于是我新增了字段来控制,只要在加载数据时,上拉下拉触发时都不请求数据
anchorDetail.js文件:
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function () {
console.log("onPullDownRefresh")
if (this.data.isLoading == false) {
this.data.isLoading = true
this.requestSliderData(false)
}
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {
console.log("onReachBottom")
if (this.data.isLoading == false) {
this.data.isLoading = true
this.requestSliderData(true)
}
},
//请求上/下一个数据
requestSliderData: function (isDownSlider) {
wx.showLoading({
title: '载入中...',
})
var that = this
var url = (isDownSlider ? 'anchor/next' : 'anchor/prev')
common.http(url, null, function (res) {
that.setData({
videoheight: 0,
canClickNextBtn: true
})
if (res.data.ok == "1") {
wx.hideLoading()
that.setData({
anchor_id: res.data.data.info.id,
detail: res.data.data.info,
hasMore: res.data.data.has_more,
share: res.data.data.info.share_info
})
} else {
if (res.data.ok == "168") {
that.setData({
hasMore: 0
})
}
wx.showToast({
title: res.data.msg,
icon: 'none',
duration: 2000
})
}
}, function (res) {
that.setData({
videoheight: 0,
canClickNextBtn: true
})
wx.showToast({
title: res.data.msg,
icon: 'none',
duration: 2000
})
}, function (res) {
if (isDownSlider) {
wx.pageScrollTo({
scrollTop: 0,
duration: 100
})
}else {
wx.stopPullDownRefresh()
}
that.data.isLoading = false
})
}
目前这种方案实现,体验性还可以。最后就视频滑动切换功能实现过程,个人感受做个总结:
1、多思考多实践多总结,问题总会有突破口和解决方案
2、注重细节和用户体验,精细化产品是从1到100的必经之路