关于个人项目(臻美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
微信公众号:前端历劫之路