基於vue3.0構建移動端仿抖音/快手短視訊+直播實戰專案Vue3-DouYin。
5G時代已來,短視訊也越來越成為新一代年輕人的娛樂方式,在這個特殊之年,又將再一次成為新年俗!
基於vue3.x+vite2+vuex4+vue-router+vant3+v3popup等技術搭建開發仿抖音App介面小視訊/直播/聊天例項專案。實現短視訊上下左右滑動切換、點贊/評論/聊天/紅包及送禮物等功能。
一、運用技術
- 編碼器:VScode/Notepad++
- 使用技術:Vue3.x+Vuex4.x+Vue-Router4
- 元件庫:Vant^3.0.4 (有贊移動端vue3元件庫)
- 彈層元件:V3Popup(基於vue3自定義彈層元件)
- 字型圖示:阿里iconfont圖示庫
- 導航欄+標籤欄:基於vue3自定義navbar/tabbar元件
二、專案目錄結構
◆ 效果預覽
◆ vue3.x自定義頂部導航+標籤欄
專案中所有頂部導航及底部tabbar均是使用vue3自定義元件來實現效果,支援自定義插槽內容。
<navbar :back="false" bgcolor="transparent" transparent> <template v-slot:title> <div class="navbar__tab"> ... </div> </template> <template v-slot:right><div><i class="iconfont icon-search"></i></div></template> </navbar> <tabbar bgcolor="linear-gradient(to bottom, transparent, rgba(0,0,0,.6))" color="rgba(255,255,255,.6)" activeColor="#fff" fixed />
◆ vue3.x全域性彈出層元件
專案中所有的彈框應用場景均是之前開發的一款vue3自定義元件v3popup來實現功能。
vue3版的自定義彈框元件,擁有20+種自定義引數配置,多種彈框型別及動畫效果。
https://www.cnblogs.com/xiaoyan2017/p/14210820.html
◆ vite.config.js配置檔案
一些簡單的vite2專案配置,可進行一些常用環境及alias路徑別名設定。
/** * 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') } }
◆ 引入公共元件
讓專案程式碼更加整潔,在plugins.js中配置一些公共元件,然後在main.js中引入即可。
/** * 引入公共元件 */ // 引入Vant3.x元件庫 import Vant from 'vant' import 'vant/lib/index.css' // 引入Vue3.x移動端彈層元件 import V3Popup from '@components/v3popup' import NavBar from '@components/navBar.vue' import TabBar from '@components/tabBar.vue' import Utils from './utils' import Storage from './storage' const Plugins = (app) => { app.use(Vant) app.use(V3Popup) // 註冊公用元件 app.component('navbar', NavBar) app.component('tabbar', TabBar) app.provide('utils', Utils) app.provide('storage', Storage) } export default Plugins
◆ vue3.x表單驗證+60s倒數計時
<!-- //登錄檔單模板 --> <template> <div> <div class="vui__scrollview vui__scrollview-lgreg flex1"> <div class="nt__lgregPanel"> <div class="lgreg-header"> <div class="slogan"> <img class="logo" src="/static/logo.png" /> <p class="text ff-gg">Vue3.0-DouYin</p> </div> <div class="forms"> <form @submit.prevent="handleSubmit"> <div class="item flexbox flex_alignc"> <input class="iptxt flex1" type="text" v-model="formObj.tel" placeholder="請輸入手機號" maxlength="11" /> </div> <div class="item flexbox flex_alignc"> <input class="iptxt flex1" type="password" v-model="formObj.pwd" placeholder="請輸入密碼" /> </div> <div class="item flexbox flex_alignc"> <input class="iptxt flex1" type="text" v-model="formObj.vcode" placeholder="驗證碼" /> <button class="btn-getcode" @click.prevent="handleVcode" :disabled="disabled">{{vcodeText}}</button> </div> <div class="item btns"> <button class="flex-c" type="submit"><i class="iconfont icon-go c-fff"></i></button> </div> <div class="item lgreg-lk"> <router-link class="navigator" to="/login">已有賬號,去登入</router-link> </div> </form> </div> </div> </div> </div> </div> </template>
<script> import { reactive, toRefs, inject, getCurrentInstance } from 'vue' export default { components: {}, setup() { const { ctx } = getCurrentInstance() const v3popup = inject('v3popup') const utils = inject('utils') const formObj = reactive({}) const data = reactive({ vcodeText: '獲取驗證碼', disabled: false, time: 0, }) const VTMsg = (content) => { v3popup({ content: `<div style='text-align:center;'><i class='iconfont icon-error'></i> ${content}</div>`, popupStyle: 'background:#ffefe6;color:#fe2c55;', position: 'top', time: 2 }) } const handleSubmit = () => { if(!formObj.tel){ VTMsg('手機號不能為空!') }else if(!utils.checkTel(formObj.tel)){ VTMsg('手機號格式不正確!') }else if(!formObj.pwd){ VTMsg('密碼不能為空!') }else if(!formObj.vcode){ VTMsg('驗證碼不能為空!') }else{ // ... } } // 倒數計時 const handleVcode = () => { if(!formObj.tel) { VTMsg('手機號不能為空!') }else if(!utils.checkTel(formObj.tel)) { VTMsg('手機號格式不正確!') }else { data.time = 60 data.disabled = true countDown() } } const countDown = () => { if(data.time > 0) { data.vcodeText = '獲取驗證碼('+ data.time +')' data.time-- setTimeout(countDown, 1000) }else{ data.vcodeText = '獲取驗證碼' data.time = 0 data.disabled = false } } return { formObj, ...toRefs(data), handleSubmit, handleVcode } } } </script>
◆ vue3.x實現小視訊功能
小視訊頁面使用了有贊元件庫中的swipe元件來實現滑動切換,開啟lazy-render讓滑動更加流暢。
<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"><img class="avatar" :src="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> import { onMounted, onUnmounted, ref, reactive, toRefs, inject, nextTick } from 'vue' import CmtEditor from '@components/cmtEditor.vue' // ... export default { components: { CmtEditor, }, 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) } // 播放進度條 const handleProgress = () => { let video = getVideoContext() if(!video) return let curTime = video.currentTime.toFixed(1) let duration = video.duration.toFixed(1) data.vdProgress = parseInt((curTime / duration).toFixed(2) * 100) } // ... // 開啟連結 const handleOpenLink = (item) => { // 監聽路由地址棧 handlePopStateOpen() data.isShowLinkPopup = true data.linkSrc = item.ads data.linkTitle = item.adstitle ? item.adstitle : '網址連結' } return { ...toRefs(data), swipeHorizontalRef, editorRef, handleTabNav, handleSwipeHorizontal, handleSwipeVertical, handlePlay, handlePause, handleVideoClicked, // ... } } } </script>
至於專案中的聊天模組就不詳細介紹了,之前有分享過一篇vue3.0開發移動端聊天例項專案,感興趣的可以去看看哈~~
https://www.cnblogs.com/xiaoyan2017/p/14250798.html
◆ vue3.x彈幕功能簡單實現
直播頁面在小視訊頁面功能基礎上新增彈幕,滾動訊息區、送禮物、充值彈窗等功能。
彈幕功能的簡單實現,共有3條滾動路線。
const data = reactive({ // ... // 彈幕佇列 idx: 2, dmLs: [ ... ], // 正在執行的彈幕佇列 dmActiveLs: [] }) onMounted(() => { // ... // 裝載彈幕 setInterval(() => { starDanMu() }, 1500) }) const starDanMu = () => { let query = null if(!query) { query = data.dmLs.shift() } if(query) { query.row = data.idx data.idx = (data.idx % 3 + 1) data.dmActiveLs.push(query) } }
<div class="lv__wrap-danmu"> <div class="danmu__bx"> <div class="danmu__ls" v-for="item in dmActiveLs" :key="item.id" :data-row="item.row" @animationend="dmAnimationEnd"> <div class="item"> <img class="avatar" :src="item.avatar" /> <p class="name">{{item.name}}</p><p class="desc">{{item.desc}}</p> </div> </div> </div> </div>
OK,以上就是使用vue3.x+vite2開發仿抖音小視訊/直播的一些分享,希望對大家有些幫助哈~~ ✍??
最後附上一個vue3網頁版聊天專案
https://www.cnblogs.com/xiaoyan2017/p/14307849.html