【首发】vue3.x+vant3直播|抖音短视频实例

举报
Andy Yan 发表于 2021/02/06 14:26:09 2021/02/06
【摘要】 随着生活品质的提高及人们观念的改变,短视频/直播获得愈来愈多新一代人的喜爱,2021又是一个直播元年。今天给大家分享的是vue3.0实战小视频+直播+聊天项目。基于vue3.0.5+vuex+vant3+v3popup等技术架构的一款移动端仿抖音|微信直播聊天互动项目。短视频页支持滑屏切换视频、点赞/评论/分享、链接查看、小商品等功能。

随着生活品质的提高及人们观念的改变,短视频/直播获得愈来愈多新一代人的喜爱,2021又是一个直播元年。

p2.gif

今天给大家分享的是vue3.0实战小视频+直播+聊天项目。基于vue3.0.5+vuex+vant3+v3popup等技术架构的一款移动端仿抖音|微信直播聊天互动项目。

p5.gif

短视频页支持滑屏切换视频、点赞/评论/分享、链接查看、小商品等功能。

p3.gif

运用技术

  • 编辑器:VScode
  • 构建工具:vite.js
  • 使用技术:Vue3.x+Vuex4.x+Vue-Router4
  • 组件库:Vant^3.0.4 (有赞移动端vue3组件库)
  • 弹层组件:V3Popup(基于vue3自定义弹层组件)
  • 字体图标:阿里iconfont图标库
  • 导航栏+标签栏:基于vue3自定义navbar/tabbar组件

未标题-pp4.png

整个项目采用分层式目录结构,非常简洁清晰,运用了vue3最新组合式语法编码开发。

360截图20210202094413416.png

vite项目配置

/**
 * 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分离公共组件

新建一个plugin.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)
}

vue3聊天功能实现

项目中的聊天模块,在之前专门有过一篇实例分享,大家感兴趣可以去看看。

p6.gif

p7.gif

https://www.cnblogs.com/xiaoyan2017/p/14250798.html

vue3短视频/直播实现

项目中的短视频和直播模板都使用到了vant3组件库中的swipe来实现滑屏切换。

360截图20210202174749058.png

整体分为顶部、视频区、底部工具栏三大模块。

<template>
    <div class="bg-161823">
        <!-- >>顶部NavBar -->
        <navbar :back="false" bgcolor="transparent" transparent>
            <template v-slot:title>
                ...
            </template>
            <template v-slot:right><div><i class="iconfont icon-search"></i></div></template>
        </navbar>

        <!-- >>主面板 -->
        <div class="vui__scrollview flex1">
            <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>
                                        </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>
        </div>

        <!-- >>底部TabBar -->
        <tabbar
            bgcolor="linear-gradient(to bottom, transparent, rgba(0,0,0,.6))"
            color="rgba(255,255,255,.6)"
            activeColor="#fff" 
            fixed
        />


        <!-- ……商品模板 -->
        <v3-popup v-model="isShowGoodsPopup" position="bottom" round xclose title="热销商品" @end="handlePopStateClose" opacity=".2">
            <div v-if="goodsLs" class="wrap_goodsList">
                ...
            </div>
        </v3-popup>

        <!-- ……评论列表模板 -->
        <v3-popup v-model="isShowReplyPopup" position="bottom" round xclose opacity=".2">
            <div class="nt__commentWrap">
                <!-- 评论列表 -->
                ...
            </div>
        </v3-popup>
        <!-- ……评论编辑器模板 -->
        <v3-popup v-model="isShowReplyEditor" position="bottom" opacity=".2">
            <div class="vui__footTool nt__commentWrap">
                ...
            </div>
        </v3-popup>

        <!-- ……分享模板 -->
        <v3-popup v-model="isShowSharePopup" anim="footer" type="actionsheet" round xclose opacity=".2"
            title="<div style='text-align:left;'>分享至</div>"
            :btns="[
                {text: '取消', style: 'color:#999;', click: () => isShowSharePopup=false},
            ]"
        >
            ...
        </v3-popup>
    </div>
</template>

<script>
/**
 * @Desc     Vue3.0实现小视频功能
 */
import { onMounted, onUnmounted, ref, reactive, toRefs, inject, nextTick } from 'vue'

import CmtEditor from '@components/cmtEditor.vue'

import videoJSON from '@/mock/videolist.js'
import emojJSON from '@/mock/cmt-emoj.js'

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 data = reactive({
            // ...
        })

        onMounted(() => {
            swipeHorizontalRef.value.swipeTo(data.activeNav, {immediate: true})

            // ...
        })

        // ...

        // 垂直切换页面事件
        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)
        }

        // ...

        return {
            ...toRefs(data),
            swipeHorizontalRef,
            editorRef,

            // ...
        }
    }
}
</script>

p11-1.gif

如上图:滑动切换还是很流畅的。如果怕卡顿,可以设置lazy-render来延迟加载下一页模板数据。

p11-2.gif

p11-3.gif

项目中的所有弹窗功能均是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.0实战项目,希望大家能喜欢哈~~

0.gif

文章来源: segmentfault.com,作者:xiaoyan2017,版权归原作者所有,如需转载,请联系作者。

原文链接:https://segmentfault.com/a/1190000039152814

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。