Vue3.0短視訊+直播|vue3+vite2+vant3仿抖音介面|vue3.x小視訊例項

xiaoyan2017發表於2021-02-02

基於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

 

相關文章