项目介绍
Vue3DouYin 基于vite2+vue3.0+vant3+v3popup
等技术开发的一款移动端仿抖音/快手App界面短视频
实例项目。实现了滑动切换视频、暂停/进度条展示、点赞/评论/聊天、弹幕/送礼物/红包
等功能。
技术栈
- 编码+技术:vscode + vite2/vue3.0/vue-router/vuex4
- UI组件库:vant3 (有赞移动端vue3组件库)
- 弹层组件:v3popup(移动端vue3弹框组件)
- 字体图标:阿里iconfont图标
- 导航条+底部栏:自定义顶部navbar/tabbar标签栏组件
项目目录结构
vue3自定义手机端弹框组件
v3popup一款使用vue3开发的mobile端自定义弹框组件。完美的融入到项目中各个弹窗场景。
由于之前写过一篇这方面的分享,大家感兴趣的话可以去看下。
vue3.0系列之自定义mobile版弹出层组件|vue3移动端弹框
vite2项目配置
/**
* Vite2项目配置
*/
import vue from '@vitejs/plugin-vue'
import path from 'path'
/**
* @type {import('vite').UserConfig}
*/
export default {
plugins: [vue()],
build: {
// 基本目录
// base: '/',
/**
* 输出文件目录
* @default dist(默认)
*/
// outDir: 'target',
},
// 环境配置
server: {
// 自定义接口
port: 3000,
// 是否自动浏览器打开
open: false,
// 是否开启https
https: false,
// 服务端渲染
ssr: false,
// 代理配置
proxy: {
// ...
}
},
// 设置路径别名
alias: {
'@': path.resolve(__dirname, './src'),
'@components': path.resolve(__dirname, './src/components'),
'@views': path.resolve(__dirname, './src/views')
}
}
vue3主入口main.js配置
引入一些路由/状态管理,公共组件及样式。
/**
* vue3.0主页面配置
*/
import { createApp } from 'vue'
import App from './App.vue'
// 引入Router及Vuex
import router from './router'
import store from './store'
// 引入公用组件
import Plugins from './plugins'
// 引入Js
import '@/assets/js/fontSize'
// 引入公用样式
import '@/assets/fonts/iconfont.css'
import '@/assets/css/reset.css'
import '@/assets/css/layout.css'
const app = createApp(App)
app.use(router)
app.use(store)
app.use(Plugins)
app.mount('#app')
vue3小视频/直播功能
使用有赞vue3组件库中的swipe组件实现小视频上下滑动轮播效果。
<div class="vui__swipeview">
<!-- ///滑动切换区 -->
<van-swipe ref="swipeHorizontalRef" :show-indicators="false" :loop="false" @change="handleSwipeHorizontal">
<van-swipe-item v-for="(item,index) in videoLs" :key="index">
<template v-if="item.category == 'nearby'">
<div class="swipe__nearLs">
...
</div>
</template>
<template v-if="item.category == 'recommend' || item.category == 'follow'">
<van-swipe vertical lazy-render :show-indicators="false" :loop="false" @change="handleSwipeVertical">
<van-swipe-item v-for="(item2, index2) in item.list" :key="index2">
<!-- ///视频模块 -->
<div class="swipe__video">
<video class="vdplayer" :id="'vd-'+index+'-'+index2" loop preload="auto"
:src="item2.src"
:poster="item2.poster"
webkit-playsinline="true"
x5-video-player-type="h5-page"
x5-video-player-fullscreen="true"
playsinline
@click="handleVideoClicked"
>
</video>
<span v-show="!isPlay" class="btn__play" @click="handleVideoClicked"><i class="iconfont icon-bofang"></i></span>
</div>
<!-- ///信息模块 -->
<div class="swipe__vdinfo flexbox flex-col">
<div class="flexbox flex-alignb">
<!-- ///底部信息栏 -->
<div class="swipe__footbar flex1">
<div v-if="item2.ads" class="item swipe__superlk ads" @click="handleOpenLink(item2)">
<i class="iconfont icon-copylink fs-28"></i>查看详情<i class="iconfont icon-arrR fs-24"></i>
</div>
<div v-if="item2.collectionLs&&item2.collectionLs.length>0" class="item swipe__superlk">
<i class="iconfont icon-copylink fs-24 mr-10"></i><div class="flex1">合集《小鬼当家》主演花絮</div><i class="iconfont icon-arrR fs-24"></i>
</div>
<div class="item uinfo flexbox flex-alignc">
<router-link to="/friend/uhome">![](item2.avatar)</router-link>
<router-link to="/friend/uhome"><em class="name">{{item2.author}}</em></router-link>
<button class="btn vui__btn vui__btn-primary" :class="item2.isFollow ? 'isfollow' : ''" @click="handleIsFollow(item.category, index2)">{{item2.isFollow ? '已关注' : '关注'}}</button>
</div>
<div class="item at">@{{item2.author}}</div>
<div v-if="item2.topic" class="item kw"><em v-for="(kw,idx) in item2.topic" :key="idx">#{{kw}}</em></div>
<div class="item desc">{{item2.desc}}</div>
</div>
<!-- ///右侧工具栏 -->
<div class="swipe__toolbar">
<div v-if="item2.goods&&item2.goods.length>0" class="item ball flexbox" @click="handleOpenGoods(item2.goods)"><i class="ico iconfont icon-cart"></i></div>
<div class="item" @click="handleIsLike(item.category, index2)"><i class="ico iconfont icon-like" :class="item2.isLike ? 'islike' : ''"></i><p class="num">{{item2.likeNum+(item2.isLike ? 1 : 0)}}</p></div>
<div class="item" @click="isShowReplyPopup=true"><i class="ico iconfont icon-liuyan"></i><p class="num">{{item2.replyNum}}</p></div>
<div class="item" @click="isShowSharePopup=true"><i class="ico iconfont icon-fenxiang"></i><p class="num">{{item2.shareNum}}</p></div>
</div>
</div>
</div>
</van-swipe-item>
</van-swipe>
</template>
</van-swipe-item>
</van-swipe>
<!-- ///底部进度条 -->
<div class="swipe__progress"><i class="bar" :style="{'width': vdProgress+'%'}"></i></div>
</div>
<script>
/**
* @Desc vue3.0+vant3小视频/直播
* @Time andy by 2021-02
* @About Q:282310962 wx:xy190310
*/
import { onMounted, onUnmounted, ref, reactive, toRefs, inject, nextTick } from 'vue'
// ...
export default {
setup() {
// 定时器
const vdTimer = ref(null)
const tapTimer = ref(null)
const swipeHorizontalRef = ref(null)
const editorRef = ref(null)
const v3popup = inject('v3popup')
// ...
// 垂直切换页面事件
const handleSwipeVertical = (index) => {
if(data.activeNav == 0) {
// 附近页
data.activeOneIdx = index
}else if(data.activeNav == 1) {
// 关注页
data.activeTwoIdx = index
// console.log('关注页索引:' + index)
}else if(data.activeNav == 2) {
// 推荐页
data.activeThreeIdx = index
// console.log('推荐页索引:' + index)
}
vdTimer.value && clearInterval(vdTimer.value)
data.vdProgress = 0
data.isPlay = false
let video = getVideoContext()
if(!video) return
video.pause()
// 重新开始
video.currentTime = 0
data.activeSwipeIndex = index
// 自动播放下一个
handlePlay()
}
// 播放
const handlePlay = () => {
console.log('播放视频...')
let video = getVideoContext()
if(!video) return
video.play()
data.isPlay = true
// 设置进度条
vdTimer.value = setInterval(() => {
handleProgress()
}, 16)
}
// 暂停
const handlePause = () => {
console.log('暂停视频...')
let video = getVideoContext()
if(!video) return
video.pause()
data.isPlay = false
vdTimer.value && clearInterval(vdTimer.value)
}
// 视频点击事件(判断单/双击)
const handleVideoClicked = () => {
console.log('触发视频点击事件...')
tapTimer.value && clearTimeout(tapTimer.value)
data.clickNum++
tapTimer.value = setTimeout(() => {
if(data.clickNum >= 2) {
console.log('双击事件')
}else {
console.log('单击事件')
if(data.isPlay) {
handlePause()
}else {
handlePlay()
}
}
data.clickNum = 0
}, 300)
}
return {
...toRefs(data),
// ...
}
}
}
</script>
直播页面送礼物/充值等弹窗都是使用v3popup组件实现。
<!-- ……送礼物模板 -->
<v3-popup v-model="isShowGiftPopup" position="bottom" round popupStyle="background:#36384a;">
<div class="wrap_giftList">
<div class="gt__hdtit flex-c">
<i class="back iconfont icon-close" @click="isShowGiftPopup=false"></i>
<div class="flex1">赠送礼物</div>
<div class="num" @click="isShowRechargePopup=true"><i class="iconfont icon-douzi fs-24"></i> 0 <i class="iconfont icon-arrR fs-24"></i></div>
</div>
<div class="gt__swipe">
<div class="gtitem" :class="giftCur == index ? 'on' : ''" v-for="(item,index) in giftLs" :key="index" @click="handleGiftClicked(item, index)">
<div class="inner flex-c flex-col">
![](item.giftPic)
<p class="gtlbl">{{item.giftLabel}}</p>
<p class="gtnum"><i class="iconfont icon-douzi"></i> {{item.giftCoins}}</p>
</div>
</div>
</div>
</div>
</v3-popup>
<!-- ……充值模板(微信豆) -->
<v3-popup v-model="isShowRechargePopup" position="bottom" round popupStyle="background:#36384a;" opacity="0">
<div class="wrap_giftList">
<div class="gt__hdtit flex-c">
<i class="back iconfont icon-arrD" @click="isShowRechargePopup=false"></i>
<div class="flex1">选择充值金额</div>
<div class="num"><i class="iconfont icon-douzi fs-24"></i> 0</div>
</div>
<div class="gt__swipe gt__recharge">
<div class="gtitem" :class="rechargeIdx == index ? 'cur' : ''" v-for="(item,index) in rechargeLs" :key="index" @click="handleRecharge(index)">
<div class="inner flex-c flex-col">
<p class="gtcoins"><i class="iconfont icon-douzi"></i> {{item.gtcoins}}</p>
<p class="gtmoney">售价{{item.gtmoney}}元</p>
</div>
</div>
<div class="pad10"><button class="vui__btn vui__btn-primary" style="border-radius:.1rem;height:40px;" @click="isShowSubmitRecharge=true">确认支付(¥{{rechargeLs[rechargeIdx].gtmoney}})</button></div>
</div>
</div>
</v3-popup>
ok,基于vue3+vite2开发短视频/直播实例就暂时分享这么多,感谢大家的支持!💪
最后附上一个Vue3+ElementPlus桌面web端聊天实例
vue3.x仿制微信/微博web版聊天室
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。