关于个人项目(臻美MV【仿抖音App】)滑动切换视频的分析(前端角度)

我们知道你天天刷抖音的时候可以上滑切换视频,互不影响。那么我们站在前端的角度能否可以实现这种效果呢?
这是我的个人项目:臻美MV

下面我是用Vue写的,现在我把它开源。


 





























































































Vue:

初始界面

<template>
  <div class="box jz">
    <div>
      <img src="../assets/MV.png" alt="">
    </div>
    <mu-button fab  color="primary" @click="go" class="go" >
      <mu-icon value="arrow_right_alt" color="white" size="34"></mu-icon>
    </mu-button>
  </div>
</template>

<script>
export default {
  name: 'index',
  data () {
    return {
      msg: ''
    }
  },
  methods: {
    go () {
      this.$router.push({
        name: 'mv',
        params: {
          id: this.$store.state.id
        }
      })
    }
  },
  mounted () {
    this.$axios.get(['/top/mv?limit=5'])
      .then((response) => {
        // success
        let num = Math.floor(Math.random() * 5 + 1)
        localStorage.setItem('i', num)
        console.log(response.data.data)
        localStorage.setItem('list', JSON.stringify(response.data.data[num]))
        this.$store.state.id = response.data.data[num].id
      })
      .catch((error) => {
        // error
        console.log(error)
      })
  }

}
</script>
<style scoped>
  @keyframes my{
    from{opacity: 0;}
    to{opacity: 1;}
  }
  .go{margin-top:60px ;}
  .jz{animation:my 0.3s;}
  .box{text-align: center;}
  .box img{width: 25%;margin-top:25vh;}
</style>

 

MV界面

<template>
  <div class="box">
    <v-carousel :cycle="false" :show-arrows="false" hide-delimiters   @change="f(index1)" v-model="index1" class="view" height="100vh">
      <v-carousel-item v-for="(item,index) in list" :key="index" class="item" @touchstart="cl">
        <div class="inner">
          <video :src="url"  autoplay="autoplay"  v-if="playIndex==index"></video>
          <div class="foot">
            <div class="in">
              <p class="user">@ {{item.artistName}}</p>
              <p class="name">{{item.name}}</p>
            </div>
            <div class="pl">
              <mu-icon value="speaker_notes" @click="openBotttomSheet" color="white" size="32"></mu-icon>
            </div>
          </div>
        </div>
      </v-carousel-item>
    </v-carousel>
    <mu-bottom-sheet :open.sync="open" class="sheet">
      <mu-load-more @refresh="refresh" :refreshing="refreshing" :loading="loading" @load="load">
        <div v-for="(item,index) in plist" :key='index' class="ovf pll">
          <div class="pl-l"><img :src="item.user.avatarUrl" alt=""></div>
          <div class="pl-r">
            <div class="name1">{{item.user.nickname}}</div>
            <div class="con">{{item.content}}</div>
          </div>
        </div>
      </mu-load-more>
    </mu-bottom-sheet>
  </div>
</template>

<script>import {mapGetters} from 'vuex'
export default {
  name: 'mv',
  data () {
    return {
      name: '',
      user: '',
      index1: '',
      url: '',
      list: [],
      num: 1,
      playIndex: null,
      open: false,
      idd: '',
      plist: '',
      num1: 0,
      refreshing: false,
      loading: false
    }
  },
  methods: {
  //下滑重新加载更多评论
    refresh () {
      this.refreshing = true
      this.$refs.container.scrollTop = 0
      setTimeout(() => {
        this.refreshing = false
        this.$axios.get(['/comment/mv?id=' + this.idd]) //这里的url我隐藏了。谢谢理解
          .then((response) => {
            // success
            console.log(response.data.comments)
            this.plist = response.data.comments
          })
          .catch((error) => {
            // error
            console.log(error)
          })
      }, 2000)
    },
    //下滑加载更多评论
    load () {
      this.loading = true
      setTimeout(() => {
        this.loading = false
        this.num1 += 10
        this.$axios.get(['/comment/mv?id=' + this.idd + '&limit=' + this.num1]) //这里的url我隐藏了。谢谢理解
          .then((response) => {
            // success
            console.log(response.data.comments)
            this.plist = response.data.comments
          })
          .catch((error) => {
            // error
            console.log(error)
          })
      }, 2000)
    },
    //右滑切换视频
    f (index) {
      console.log(index)
      let id = this.list[index].id
      this.idd = id
      this.$axios.get(['/mv/url?id=' + id])//这里的url我隐藏了。谢谢理解
        .then((response) => {
          // success
          console.log(response.data.data.url)
          this.url = response.data.data.url
          this.playIndex = index
        })
        .catch((error) => {
          // error
          console.log(error)
        })
    },
    //关闭评论
    closeBottomSheet () {
      this.open = false
    },
    //打开评论
    openBotttomSheet () {
      this.open = true
      console.log(this.idd)
      this.$axios.get(['/comment/mv?id=' + this.idd])//这里的url我隐藏了。谢谢理解
        .then((response) => {
          // success
          console.log(response.data.comments)
          this.plist = response.data.comments
        })
        .catch((error) => {
          // error
          console.log(error)
        })
    },
    //数据自动增加点击增加
    cl () {
      this.num++
      this.$axios.get(['/top/mv?limit=' + this.num])//这里的url我隐藏了。谢谢理解
        .then((response) => {
          // success
          console.log(response.data.data)
          this.list = response.data.data
        })
        .catch((error) => {
          // error
          console.log(error)
        })
    }
  },
  computed: {
    ...mapGetters({
      getid: 'getid'
    })
  },
  //初始化数据
  mounted () {
    const list = localStorage.getItem('list')
    this.idd = this.$route.params.id
    this.list.push(JSON.parse(list))
    console.log(this.list)
    this.index1 = localStorage.getItem('list')
    this.$axios.get(['/mv/url?id=' + this.$route.params.id])//这里的url我隐藏了。谢谢理解
      .then((response) => {
        // success
        this.playIndex = localStorage.getItem('i')
        console.log(response.data.data.url)
        this.url = response.data.data.url
      })
      .catch((error) => {
        // error
        console.log(error)
      })
  }
}
</script>
<style scoped>
  html,body{position: relative;}
  .box{width: 100%;height: 100vh;background: #333;}
  .sheet{height: 70vh;overflow: auto;border-top-right-radius:0.2rem;border-top-left-radius:0.2rem;z-index: 1000}
  .inner{width: 100%;height: 100vh;position: relative;}
  .in{width: 80%;float: left;}
  .pl{width: 10%;float: right;margin-top:0.2rem ;}
  .foot{width: 90%;position: absolute;bottom: 14vh;left:5% ;}
  .name{color: #fff;font-size: 20px;font-weight: bold;}
  .user{color: #fff;margin-bottom:0.3rem;font-size: 16px;}
  video{width: 100%;height:auto;margin-top:35vh;}
  .item{width: 100%;height: 100vh;}
  .view{width: 100%;height: 100vh !important;}
  .pll{width: 95%;margin: 10px auto;overflow: hidden;padding: 10px 0;}
  .pl:last-child{border:none;}
  .pl-l{width: 20%;float: left;}
  .pl-l img{width: 60%;border-radius: 50%;}
  .pl-r{width: 78%;float: left;}
  .name1{font-size:14px;color: #666;font-weight: bold;margin-bottom: 5px;}
  .con{width: 100%;line-height: 24px;color: #333333;font-size:16px;}
</style>

 






我还有微信小程序,这里我也分享给大家。
微信小程序:

 




























































































MV界面

wxml

<!--pages/video/video.wxml-->
<view class="container">
  <view class="page-body">
    <view class="page-section page-section-spacing swiper">
      <swiper   current="{{current}}" bindchange='onSlideChangeEnd'
        autoplay="{{autoplay}}" circular="{{circular}}" vertical="{{vertical}}"
        interval="{{interval}}" duration="{{duration}}" previous-margin="{{previousMargin}}px" next-margin="{{nextMargin}}px"  >
        <block wx:for="{{vlists}}"   wx:key="index" wx:for-item="item">
          <swiper-item   bindtouchstart="touchStart">
              <view class="swiper-item "   >
                  <view><image src="https://www.maomin.club/data/pl.png" class="pl" bindtap='showFrame' id='i{{index}}'></image></view>
                  <video src="{{url}}"  wx:if='{{playIndex==index}}' autoplay="{{true}}"  controls='{{controls}}'  id="{{index}}"  ></video>
                  <view wx:if="{{index==0}}"  class="start" bindtap="cli">点我一下,精彩MV马上开始</view>
                  <view class="foot">
                            <view class="songer">@ {{item.artistName}}</view>
                            <view class="name">{{item.name}}</view>  
                  </view>
                    <view wx:if='{{flag}}'>
                    <view class='wrap {{wrapAnimate}}' style='background:rgba(0,0,0,{{bgOpacity}});'></view>
                    <view catchtap='hideFrame' class='frame-wrapper {{frameAnimate}}'>
                      <view catchtap='catchNone' class='frame' >
                           <scroll-view class="title-wrapper" scroll-y style="width: 100%"  bindscrolltolower='down1'  id='s{{index}}'>
                          <view   wx:for="{{plist}}"    wx:key="index" wx:for-item="item" class="plitem">
                            <view class="plitem_l">
                              <image src="{{item.user.avatarUrl}}"></image>
                            </view>
                            <view  class="plitem_r">
                              <view class="username">{{item.user.nickname}}</view>
                              <view class="content"> {{item.content}}</view>
                            </view>
                          </view>
                          </scroll-view>
                      </view>
                    </view>
                  </view>
             </view>
          </swiper-item>
        </block>
      </swiper>
    </view>
  </view>
</view>


 

wxss

/* pages/video/video.wxss */
page { height: 100%; font-size: 32rpx; background: #000; }
swiper{ height:100vh; }
.swiper-item{ display: block; height:100%; position: relative; align-items: center; justify-content: center; font-size: 36rpx; }
.cover{ width: 100%; height:100vh ; }
.name{ color: #fff; font-size: 34rpx; font-weight: bold; }
video{ width: 100%; height: 100vh; }
.start{ color: #fff; text-align: center; line-height: 65vh; }
.songer{ color: #fff; font-size: 26rpx; margin-bottom:25rpx; }
.pl{ width:10%; height:74rpx; float: right;position: absolute;right: 4%;bottom:13vh;z-index: 100; }
.foot{width: 70%; position: absolute; bottom: 10%; left: 5%; }
.wrapAnimate{animation: wrapAnimate 0.5s ease-in-out forwards}
@keyframes wrapAnimate{
  0%{}
  100%{background:rgba(0,0,0,0.35);}
}
.wrapAnimateOut{animation: wrapAnimateOut 0.4s ease-in-out forwards}
@keyframes wrapAnimateOut{
  0%{background:rgba(0,0,0,0.35);}
  100%{background:rgba(0,0,0,0);}
}
.frameAnimate{animation: frameAnimate 0.5s ease forwards;}
@keyframes frameAnimate{
  0%{}
  100%{opacity: 1;top:0vh;}
}
.frameAnimateOut{animation: frameAnimateOut 0.4s ease forwards;}
@keyframes frameAnimateOut{
  0%{opacity: 1;top:0vh;}
  100%{opacity: 0;top:100vh;}
}
.frame-wrapper{position: fixed;height:100vh;width:100vw;z-index: 2;top: 50vh;}
.frame{background: #fff; position: absolute;bottom: 0;width: 88.2vw;padding: 5.9vw 5.9vw 0;border-top-left-radius: 20rpx;border-top-right-radius: 20rpx;z-index: 3;}
.title-wrapper { justify-content: space-between; font-size: 4.9vw; color: #4a4a4a; margin-bottom: 5.9vw; height: 70vh; }
.title-wrapper>image{width:3.2vw;height:3.2vw;padding:0 5vw;margin-right:-5vw;}
.flex{display: flex;align-items: center;}
.wrap{position: fixed;z-index: 1;top: 0;left: 0;right: 0;bottom: 0;}
::-webkit-scrollbar { width: 0; height: 0; color: transparent; }
.plitem{ overflow: hidden; margin:45rpx 0; }
.plitem_l{ width: 12%; float: left; }
.plitem_l image{ width: 100%; height: 78rpx; border-radius:50%; margin-top:10rpx; }
.plitem_r{ width: 84%; float: right; }
.username{ font-size: 28rpx; color: #666; margin-bottom:18rpx; }
.content{ font-size: 28rpx; color: #333; }

 

js

Page({
  data: {
    vlists:'',
    vertical: true,
    autoplay: false,
    controls:false,
    circular: false,
    interval: 2000,
    duration: 1000,
    previousMargin: 0,
    nextMargin: 0 ,
    num:1,
    iid:'',
    url:'',
    current:0,
    playIndex: null,
    flag: false,
    wrapAnimate: 'wrapAnimate',
    bgOpacity: 0,
    frameAnimate: 'frameAnimate',
    plist:'',
    con:1
  },
  // 点击
  cli:function () {
    var that = this
              wx.request({
                url: '/mv/url?id=' + that.data.vlists[0].id, //这里的url我隐藏了。谢谢理解
                header: {
                  'content-type': 'application/json' // 默认值
                },
                success(res) {
                  that.setData({
                    url: res.data.data.url
                  })
                  that.setData({
                        playIndex: 0
                      })
                }
              })
  },
  // 滚动到底部
  down1:function (e) {
    var that = this
    console.log(e.currentTarget.id)
    var i = e.currentTarget.id;
    var b = i.substr(1, 1)
    that.data.con+=20
     console.log(that.data.con)
     wx.request({
       url: '/comment/mv?id=' + that.data.vlists[b].id + '&limit=' + that.data.con,//这里的url我隐藏了。谢谢理解
       header: {
         'content-type': 'application/json' // 默认值
       },
       success(res) {
         console.log(res.data.comments)
         that.setData({
           plist: res.data.comments
         })
       }
     })
  },
  // 弹窗
  showFrame(e) {
    const that = this;
    console.log(e.currentTarget.id)
    var i = e.currentTarget.id;
    var b=i.substr(1,1)
    console.log(b)
              wx.request({
                url: '/comment/mv?id=' + that.data.vlists[b].id + '&offset=' + that.data.con,//这里的url我隐藏了。谢谢理解
                header: {
                  'content-type': 'application/json' // 默认值
                },
                success(res) {
                  console.log(res.data.comments)
                  that.setData({
                    plist: res.data.comments
                  })
                }
              })
      this.setData({
        flag: true,
        wrapAnimate: 'wrapAnimate',
        frameAnimate: 'frameAnimate'
      });
    },
    hideFrame() {
      const that = this;
      that.setData({
        wrapAnimate: 'wrapAnimateOut',
        frameAnimate: 'frameAnimateOut'
      });
      setTimeout(() => {
        that.setData({
          flag: false
        })
      }, 400)
    },
    catchNone() {
      //阻止冒泡
    },
    _showEvent() {
      this.triggerEvent("showEvent");
    },
    _hideEvent() {
      this.triggerEvent("hideEvent");
    },
  // 滑动事件:
  onSlideChangeEnd: function (e) {
     var that = this
      console.log('本页:'+e.detail.current)
          wx.request({
            url: '/mv/url?id=' + that.data.vlists[e.detail.current].id, //这里的url我隐藏了。谢谢理解
            header: {
              'content-type': 'application/json' // 默认值
            },
            success(res) {
              that.setData({
                playIndex: e.detail.current
              })
              that.setData({
                  url: res.data.data.url
                })
            }
          })

  },
  // 触摸开始事件  
  touchStart: function (e) {
 var that = this
 that.data.num++
 wx.request({
   url: '/top/mv?limit=' + that.data.num, //这里的url我隐藏了。谢谢理解
   header: {
     'content-type': 'application/json' // 默认值
   },
   success(res) {
     console.log(res.data.data)
     that.setData({
       vlists: res.data.data
     })
 
   }
 })
    },
  // 加载
  onLoad:function () {
    var that = this
    wx.request({
      url: '/top/mv?limit=1', //这里的url我隐藏了。谢谢理解
      header: {
        'content-type': 'application/json' // 默认值
      },
      success(res) {
        console.log(res.data.data)
           that.setData({
           vlists: res.data.data
        })
      }
      })

  }
})

 


 

作者:Vam的金豆之路

主要领域:前端开发

我的微信:maomin9761

微信公众号:前端历劫之路