效果图:
一、外层
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>