We know you can swipe up and switch videos every day without affecting each other. Can we do that from the front end? This is my personal project: Perfect MV

I wrote the following in Vue, which I now open source.

Vue:

The initial interface

<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.3 s; }.box{text-align: center; }.box img{width: 25%;margin-top:25vh; }</style>

Copy the code

The MV interface

<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: {
  // Slide to reload more comments
    refresh () {
      this.refreshing = true
      this.$refs.container.scrollTop = 0
      setTimeout((a)= > {
        this.refreshing = false
        this.$axios.get(['/comment/mv? id=' + this.idd]) // The url here is hidden. Thank you for your understanding
          .then((response) = > {
            // success
            console.log(response.data.comments)
            this.plist = response.data.comments
          })
          .catch((error) = > {
            // error
            console.log(error)
          })
      }, 2000)},// Slide to load more comments
    load () {
      this.loading = true
      setTimeout((a)= > {
        this.loading = false
        this.num1 += 10
        this.$axios.get(['/comment/mv? id=' + this.idd + '&limit=' + this.num1]) // The url here is hidden. Thank you for your understanding
          .then((response) = > {
            // success
            console.log(response.data.comments)
            this.plist = response.data.comments
          })
          .catch((error) = > {
            // error
            console.log(error)
          })
      }, 2000)},// Right swipe to switch videos
    f (index) {
      console.log(index)
      let id = this.list[index].id
      this.idd = id
      this.$axios.get(['/mv/url? id=' + id])// The url here is hidden. Thank you for your understanding
        .then((response) = > {
          // success
          console.log(response.data.data.url)
          this.url = response.data.data.url
          this.playIndex = index
        })
        .catch((error) = > {
          // error
          console.log(error)
        })
    },
    // Close comments
    closeBottomSheet () {
      this.open = false
    },
    // Open comments
    openBotttomSheet () {
      this.open = true
      console.log(this.idd)
      this.$axios.get(['/comment/mv? id=' + this.idd])// The url here is hidden. Thank you for your understanding
        .then((response) = > {
          // success
          console.log(response.data.comments)
          this.plist = response.data.comments
        })
        .catch((error) = > {
          // error
          console.log(error)
        })
    },
    // Automatic data increment click increment
    cl () {
      this.num++
      this.$axios.get(['/top/mv? limit=' + this.num])// The url here is hidden. Thank you for your understanding
        .then((response) = > {
          // success
          console.log(response.data.data)
          this.list = response.data.data
        })
        .catch((error) = > {
          // error
          console.log(error)
        })
    }
  },
  computed: {
    ...mapGetters({
      getid: 'getid'})},// Initialize the data
  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])// The url here is hidden. Thank you for your understanding
      .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.2 rem;border-top-left-radius:0.2 rem;z-index: 1000}
  .inner{width: 100%;height: 100vh;position: relative; }.in{width: 80%;float: left; }.pl{width: 10%;float: right;margin-top:0.2 rem; }.foot{width: 90%;position: absolute;bottom: 14vh;left:5%; }.name{color: #fff;font-size: 20px;font-weight: bold; }.user{color: #fff;margin-bottom:0.3 rem;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>

Copy the code

I also have wechat small program, here I also share with you.

Wechat applets:

The MV interface

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">Click on me. The music video is about to start</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, {{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>


Copy the code

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.5 s ease-in-out forwards}
@keyframes wrapAnimate{
  0%{}
  100%{background:rgba(0,0,0,0.35);}
}
.wrapAnimateOut{animation: wrapAnimateOut 0.4 s 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.5 sease forwards; } @keyframes frameAnimate{
  0%{}
  100%{opacity: 1;top:0vh;}
}
.frameAnimateOut{animation: frameAnimateOut 0.4 sease 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.2 vw;padding: 5.9 vw 5.9 vw 0;border-top-left-radius: 20rpx;border-top-right-radius: 20rpx;z-index: 3; }.title-wrapper { justify-content: space-between; font-size: 4.9 vw; color: #4a4a4a; margin-bottom: 5.9 vw; height: 70vh; }
.title-wrapper>image{width:3.2 vw;height:3.2 vw;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; }
Copy the code

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
  },
  / / click
  cli:function () {
    var that = this
              wx.request({
                url: '/mv/url? id=' + that.data.vlists[0].id, // The url here is hidden. Thank you for your understanding
                header: {
                  'content-type': 'application/json' / / the default value
                },
                success(res) {
                  that.setData({
                    url: res.data.data.url
                  })
                  that.setData({
                        playIndex: 0}}}})),// Scroll to the bottom
  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,// The url here is hidden. Thank you for your understanding
       header: {
         'content-type': 'application/json' / / the default value
       },
       success(res) {
         console.log(res.data.comments)
         that.setData({
           plist: res.data.comments
         })
       }
     })
  },
  / / window
  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,// The url here is hidden. Thank you for your understanding
                header: {
                  'content-type': 'application/json' / / the default value
                },
                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((a)= > {
        that.setData({
          flag: false})},400)
    },
    catchNone() {
      // Prevent bubbling
    },
    _showEvent() {
      this.triggerEvent("showEvent");
    },
    _hideEvent() {
      this.triggerEvent("hideEvent");
    },
  // Slide event:
  onSlideChangeEnd: function (e) {
     var that = this
      console.log('This page:'+e.detail.current)
          wx.request({
            url: '/mv/url? id=' + that.data.vlists[e.detail.current].id, // The url here is hidden. Thank you for your understanding
            header: {
              'content-type': 'application/json' / / the default value
            },
            success(res) {
              that.setData({
                playIndex: e.detail.current
              })
              that.setData({
                  url: res.data.data.url
                })
            }
          })

  },
  // Touch the start event
  touchStart: function (e) {
 var that = this
 that.data.num++
 wx.request({
   url: '/top/mv? limit=' + that.data.num, // The url here is hidden. Thank you for your understanding
   header: {
     'content-type': 'application/json' / / the default value
   },
   success(res) {
     console.log(res.data.data)
     that.setData({
       vlists: res.data.data
     })
  
   }
 })
    },
  / / load
  onLoad:function () {
    var that = this
    wx.request({
      url: '/top/mv? limit=1'.// The url here is hidden. Thank you for your understanding
      header: {
        'content-type': 'application/json' / / the default value
      },
      success(res) {
        console.log(res.data.data)
           that.setData({
           vlists: res.data.data
        })
      }
      })

  }
})
Copy the code

If you have any questions, please comment below and I will get back to you in time.