滑动切换视频功能的尝试

最近版本迭代中的需求里有这么一条:视频详情页面,页面上下滑实现视频上一个/下一个的切换

拿到这个需求,我的思路有大致以下几种方案实现:
1、使用scroll-view或者swiper实现切换
分析:由于页面有video,文档有明确提示勿在 scroll-view、swiper中使用video
结论:该方案显然不可行 ❌

2、使用刷新实现切换
分析:直接监听用户下拉动作和上拉触底操作,实现数据请求渲染即可
结论:该方案显然可行 ✅✅

3、监听触摸手势事件,确认滑动方向,实现切换
分析:直接在video上绑定监听触摸手势的touch事件,根据触摸点相对位移确定滑动方向,进而实现上下滑动时请求新数据渲染页面
结论:该方案原理上是可行的 ✅

根据上述分析,我想验证方案3的可行性,问了下需求的紧急程度和时间进度,心里做了个预估,时间方面允许,于是我便开始代码验证。下面只抽取该功能相关的代码进行展示。

anchorDetail.wxml文件:

 
   

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文件:

 
  
   
   

anchorDetail.wxss文件新增的cover-view样式:

/*宽高没有设置100%,因为会盖住其他按钮的tap事件*/
.outCoverView {
  position: fixed;
  width: 644rpx; 
  height: 1080rpx;
  background-color: red;
  top: 128rpx;
  left: 0;
}

模拟器运行,发现不触发touch事件,并且控制台日志中有警告:VM5118:2 '' 暂不支持 'bindtouchstart' 事件。换真机运行时,控制台有打印触发了touch事件的。再看看安卓机预览效果,cover-view的touch事件没响应。猜想这个就算实现,应该也会引起问题,毕竟官方开发工具存在警告信息。另外,安卓手机上预览时发现无法触发触摸手势事件。

接下来继续找问题找解决方案吧~

我尝试用画布canvas处理touch事件,

anchorDetail.wxml文件:

 
   
  

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.jsonwindow选项中或页面配置中开启enablePullDownRefresh
    • 当处理完数据刷新后,wx.stopPullDownRefresh可以停止当前页面的下拉刷新。
  • onReachBottom: 上拉触底

    • 监听用户上拉触底事件。
    • 可以在app.jsonwindow选项中或页面配置中设置触发距离onReachBottomDistance
    • 在触发距离内滑动期间,本事件只会被触发一次。

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的必经之路

你可能感兴趣的:(滑动切换视频功能的尝试)