uniapp实现仿抖音效果滑屏播放视频

4,125 阅读1分钟

效果图:

1637226845000.jpg

一、外层

1.通过swiper 并设置vertical='true'实现纵向滑动效果 同时设置circular为true实现循环滑动

2.设置三个swiper-item 通过调节index下标 用以前后视频的预加载与请求 同时设置好id 以便于使用this.$ref调用相应PlayItem内的方法

 <swiper @change="swiperChange" vertical='true' class="palycontainer" :circular="iscircular"
      	:current="currentIndex" @touchstart="start" @touchend="end" >
        	<swiper-item v-for="(item,index) in itemlist" :key='index'>
            <PlayItem @back='back' :PlayerItem='PlayerItem' :ref='item.id'/>
          </swiper-item>
    </swiper>

3.使用@touchstart="start" @touchend="end"获取对应的滑动起始坐标 判断是上滑下滑

 //data内
//滑动监听
        startData: {
        	clientY: "",
        	clientX: "",
        },
        endData: {
        	clientY: "",
        	clientX: "",
        },
   //methods内
    start(e){
        this.startData.clientX = e.changedTouches[0].clientX;
        this.startData.clientY = e.changedTouches[0].clientY;
      },
      end(e){
          this.endData.clientX = e.changedTouches[0].clientX;
          this.endData.clientY = e.changedTouches[0].clientY;
      },
      // swiper切换时判断上滑下滑 以及控制PlayItem内的video播放与暂停
      swiperChange(e){
        // sweiper切换 控制video播放与暂停
        for(var i=0;i<this.itemlist.length;i++){
          if(i!==e.detail.current){
             this.$refs[`play${i}`][0].outpause()
          }
        }
        this.$refs[`play${e.detail.current}`][0].outplay()
        // 判断是上滑下滑
        const subX = this.endData.clientX - this.startData.clientX;
        const subY = this.endData.clientY - this.startData.clientY;
        if(subY>30){
          console.log('上滑')
        }
        if(subY<-30){
          console.log('下滑')
        }
      }

二、PlayItem内

1.使用uniapp的video组件 通过this.$ref调用video的播放暂停方法

  • autoplay开启自动播放
  • poster 设置封面
  • src 设置播放路径
  • show-center-play-btn 关闭暂停按钮显示 方便换成自己的暂停样式
<video ref='myVideo' id="myVideo" :src="PlayerItem.videosrc" @error="videoErrorCallback"
        enable-danmu danmu-btn controls style="width: 100%;height: 100%;" :autoplay='autoplay'
        :poster='PlayerItem.poster'
        :show-center-play-btn='showplaybtn'
        :enable-play-gesture='enableplaygesture'
        @play='play'
        @pause='pause'
        @ended='ended'
        ></video>

三、完整代码

1.外层

<template>
  <div>
    <div >
      <swiper @change="swiperChange" vertical='true' class="palycontainer" :circular="iscircular"
      	:current="currentIndex" @touchstart="start" @touchend="end" >
        	<swiper-item v-for="(item,index) in itemlist" :key='index'>
            <PlayItem @back='back' :PlayerItem='PlayerItem' :ref='item.id'/>
          </swiper-item>
      </swiper>
    </div>

  </div>
</template>

<script>
  import PlayItem from './playItem.vue'
  export default {
    components:{
      PlayItem
    },
    data(){
      return {
        currentIndex:0,
        iscircular:true,
        PlayerItem:{
          name: '重庆最美高校景象',
          school: '重庆大学',
          schoolurl: 'https://cdn.img.up678.com/icon/school/02重庆大学1.jpg',
          coverurl: 'https://cdn.img.up678.com/ueditor/upload/image/20211112/1636697557634091425.jpg',
          view: '20w',
          vote: '4.8k',
          id: '021424',
          videosrc: 'https://img.cdn.aliyun.dcloud.net.cn/guide/uniapp/%E7%AC%AC1%E8%AE%B2%EF%BC%88uni-app%E4%BA%A7%E5%93%81%E4%BB%8B%E7%BB%8D%EF%BC%89-%20DCloud%E5%AE%98%E6%96%B9%E8%A7%86%E9%A2%91%E6%95%99%E7%A8%8B@20200317.mp4',
          poster:'https://cdn.img.up678.com/ueditor/upload/image/20211112/1636697557634091425.jpg',
          comment:[{title:'人文与艺术类',number:'1.3w',id:parseInt(Math.random()*10000),active:false,},
           {title:'永远的神',number:'1.3w',id:parseInt(Math.random()*10000),active:false,},
          {title:'这个也太ku了吧',number:'300',id:parseInt(Math.random()*10000),active:false,},
          {title:'LOVE',number:'1w',id:parseInt(Math.random()*10000),active:false,},
          {title:'这个也太ku了吧',number:'300',id:parseInt(Math.random()*10000),active:false,},
          ]
        },
        itemlist:[{id:'play0'},{id:'play1'},{id:'play2'}],
        //滑动监听
        startData: {
        	clientY: "",
        	clientX: "",
        },
        endData: {
        	clientY: "",
        	clientX: "",
        },
      }
    },
    mounted() {
      // 首屏播放
      this.$refs[`play0`][0].outplay()
    },
    methods:{
      back(){
        uni.navigateBack({
        	delta: 1,
        	// success: function() {
        	// 	beforePage.$vm.init(); // 执行前一个页面的刷新
        	// }
        });
      },
      start(e){
        this.startData.clientX = e.changedTouches[0].clientX;
        this.startData.clientY = e.changedTouches[0].clientY;
      },
      end(e){
          this.endData.clientX = e.changedTouches[0].clientX;
          this.endData.clientY = e.changedTouches[0].clientY;
      },

      swiperChange(e){
        // sweiper切换 控制video播放与暂停
        for(var i=0;i<this.itemlist.length;i++){
          if(i!==e.detail.current){
             this.$refs[`play${i}`][0].outpause()
          }
        }
        this.$refs[`play${e.detail.current}`][0].outplay()
        // 判断是上滑下滑
        const subX = this.endData.clientX - this.startData.clientX;
        const subY = this.endData.clientY - this.startData.clientY;
        if(subY>30){
          console.log('上滑')
        }
        if(subY<-30){
          console.log('下滑')
        }
      }
    }
  }
</script>

<style lang="stylus" scoped>
  .palycontainer {
  	width: 100%;
  	height: 100vh;
  }
</style>

2.PlayItem内部

<template>
  <div class='palyItem'>
    <div class='top'>
      <div class='back' @click='back()'>
        <u-icon name="play-left-fill" color="#FFFFFF" size="24"></u-icon>
        <p>返回活动主页</p>
      </div>
      <p class='id'>ID:021424</p>
      <p class='title'>{{PlayerItem.name}}</p>
      <div class='schoolbox'>
        <div class='left'>
          <image :src="PlayerItem.schoolurl" mode="" class='schoolimg'></image>
          <span class='shcoolname'>{{PlayerItem.school}}</span>
        </div>
        <div class='right'>
          <div class='number'>
            <p>{{PlayerItem.view}}观看</p>
          </div>
          <div class='number'>
            <p>{{PlayerItem.vote}}投票</p>
          </div>
        </div>
      </div>

    </div>
    <div class='playbox'>
      <video ref='myVideo' id="myVideo" :src="PlayerItem.videosrc" @error="videoErrorCallback"
        enable-danmu danmu-btn controls style="width: 100%;height: 100%;" :autoplay='autoplay'
        :poster='PlayerItem.poster'
        :show-center-play-btn='showplaybtn'
        :enable-play-gesture='enableplaygesture'
        @play='play'
        @pause='pause'
        @ended='ended'
        ></video>
        <image src="https://cdn.img.up678.com/ueditor/upload/image/20211116/1637029270675001981.png" mode="" class='pause' v-if="!showpause" @click="handleplay"></image>
    </div>
    <div class='containerbox'>
        <div class='commentcontainer'>
           <fc-bubbles click  v-for="(item,index) in PlayerItem.comment" >
                <p class='item'  @click='handlecomment(item)' :class="item.active?'active':''">
                  {{`${item.title} ${item.number}`}}个赞
                </p>
            </fc-bubbles>
        </div>
    </div>
    <div class='footer'>
      <div class='share'>
        分享
      </div>
      <div class='vote'>
        投TA一票
      </div>
    </div>
    <div class='more'>
      滑动查看下一个
    </div>
  </div>
</template>

<script>
  export default {
    props: {
      PlayerItem: {
        type: Object,
        default: {}
      }
    },
    data() {
      return {
          autoplay:false,
          showplaybtn:false,
          enableplaygesture:true,
          showpause:false,
          bubblesactive:false,
      }
    },
    onReady: function(res) {
      // #ifndef MP-ALIPAY
      this.videoContext = uni.createVideoContext('myVideo')
      // #endif
    },
    methods: {
      videoErrorCallback: function(e) {
        uni.showModal({
          content: e.target.errMsg,
          showCancel: false
        })
      },
      play(){
        this.showpause=true
      },
      pause(){
         this.showpause=false
      },
      ended(){
        this.showpause=false
      },
      handleplay(){
        this.$refs['myVideo'].play()
      },
      // 选择评论条目
      handlecomment(item){
        this.PlayerItem.comment.map((items)=>{
          if(items.id===item.id){
            if(items.active===true){
                items.active=false
            }else{
               items.active=true
            }
          }
        })
      },
      // 返回上一页
      back(){
        this.$emit('back')
      },
      // 外部滑动控制播放暂停
      outplay(){
         this.$refs['myVideo'].play()
      },
      outpause(){
         this.$refs['myVideo'].pause()
      }
    }
  }
</script>

<style lang="stylus" scoped>
  fc-bubbles{
    --color:#63a6ff;
  }
  .palyItem {
    width 100%;
    height 100vh;
    background: #0C0745;

    .top {
      height 155px;
      padding 14px 20px;
      background url('https://cdn.img.up678.com/ueditor/upload/image/20211116/1637025961257096818.png') center /100% 100%;
      box-sizing border-box;
      .back {
        width: 125px;
        height: 31px;
        border-radius: 16px;
        border: 1px solid #FFFFFF;
        display flex;
        justify-content center;
        align-items center;
        box-sizing border-box;

        p {
          height: 12px;
          font-size: 14px;
          font-family: PingFangSC-Regular, PingFang SC;
          font-weight: 400;
          color: #FFFFFF;
          line-height: 12px;
          margin-left 7px;
        }
      }

      .id {
        width: 70px;
        height: 12px;
        font-size: 15px;
        font-family: PingFangSC-Medium, PingFang SC;
        font-weight: 500;
        color: #FFFFFF;
        line-height: 12px;
        margin 19px 0 12px 0;
      }

      .title {
        height: 14px;
        font-size: 18px;
        font-family: PingFangSC-Medium, PingFang SC;
        font-weight: 500;
        color: #FFFFFF;
        line-height: 14px;
      }

      .schoolbox {
        display flex;
        justify-content space-between;
        align-items center;
        margin 13px 0 0 0;

        .left {
          display flex;
          align-items center;

          .schoolimg {
            display block;
            width 26px;
            height 26px;
            margin-right 8px;
            border-radius 50%;
          }

          .shcoolname {
            height: 14px;
            font-size: 12px;
            font-family: PingFangSC-Regular, PingFang SC;
            font-weight: 400;
            color: #FFFFFF;
            line-height: 14px;
          }
        }

        .right {
          display flex;
          align-items center;

          .number {
            height: 18px;
            line-height 18px;
            background: rgba(255, 255, 255, 0.3);
            border-radius: 4px;
            margin-left 5px;
            padding 2px 5px;

            p {
              height: 18px;
              line-height 18px;
              text-align center;
              font-size: 10px;
              font-family: PingFangSC-Regular, PingFang SC;
              font-weight: 400;
              color: rgba(255, 255, 255, 0.3);
            }
          }
        }
      }
    }

    .playbox {
      width 100%;
      height 260px;
      position relative;
      .pause{
        display block;
        width 52px;
        height 52px;
        position absolute;
        left 50%
        top 50%;
        transform translate(-26px,-26px)
      }
    }
    .containerbox{
      width 100%;
      padding 20px 30px 0px 30px;
      box-sizing border-box;
      .commentcontainer{
        width 100%;
        display flex;
        flex-wrap wrap;
        border-bottom 1px dashed #fff;
        padding-bottom 10px;
        .item{
          padding 7px 10px;
          text-align center;
          background: #F6F8FF;
          border-radius: 16px;
          height: 10px;
          font-size: 10px;
          font-family: PingFangSC-Medium, PingFang SC;
          font-weight: 500;
          color: #241D4A;
          line-height: 10px;
          margin-right 10px;
          margin-bottom 10px;
        }
        .active{
          background: #63A6FF;
          color #fff
        }
      }
    }
    .footer{
      display flex;
      justify-content center;
      align-items center;
      margin-top 28px;
      margin-bottom 28px;
      .share{
         text-align center;
        width: 56px;
        height: 34px;
        background: #FFFFFF;
        border-radius: 17px;
        border: 1px solid #0E0E63;
        font-size: 14px;
        font-family: PingFangSC-Regular, PingFang SC;
        font-weight: 400;
        color: #241D4A;
        line-height: 34px;
        margin-right 10px;
      }
      .vote{
        text-align center;
        width: 93px;
        height: 34px;
        background: linear-gradient(180deg, #8152E2 0%, #3D29C0 100%);
        border-radius: 17px;
        font-size: 14px;
        font-family: PingFangSC-Medium, PingFang SC;
        font-weight: 500;
        color: #FFFFFF;
        line-height: 34px;
      }
    }
    .more{
      width 100%;
      text-align center;
      height: 14px;
      font-size: 10px;
      font-family: PingFangSC-Medium, PingFang SC;
      font-weight: 500;
      color: #918CCC;
      line-height: 14px;
    }
  }
</style>