美文网首页
滑动切换视频功能的尝试

滑动切换视频功能的尝试

作者: 舒小妮儿 | 来源:发表于2018-07-25 11:06 被阅读318次

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

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

    相关文章

      网友评论

          本文标题:滑动切换视频功能的尝试

          本文链接:https://www.haomeiwen.com/subject/lrtqpftx.html