滑动切换视频功能的尝试

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

拿到这个需求,我的思路有大致以下几种方案实现:
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.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的必经之路

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342