vue3.0高仿快手/抖音小视频|vue3+vite2直播聊天实例|vue3.x实战短视频

项目概述

使用vite2构建工具搭建的仿抖音短视频|直播实战项目,使用了vue3.0+vuex4+vue-router+vant3+v3-popup等技术。实现了小视频上下切换滑动、点赞/评论、聊天、弹幕/送礼物/红包等功能。

技术栈

  • 编辑器:Vscode
  • MVVM框架:Vue^3.0.5
  • 状态管理:Vuex^4.0.0-rc.2
  • 页面路由:Vue-Router^4.0.3
  • UI组件库:Vant^3.0.4 (有赞手机端vue3组件库)
  • 弹框组件:v3popup(基于vue3自定义手机端弹出层组件)
  • 字体图标:阿里iconfont图标
  • 顶部条+底部栏:基于vue3自定义navbar/tabbar组件

项目目录结构

Vue3自定义手机端弹窗组件

基于vue3.0开发的一款mobile版自定义弹窗组件v3popup。拥有超多的自定义参数配置和弹窗效果。

vue3.x自定义弹框组件|vue3.0移动端弹窗|vue3全局组件
整个项目中所有弹窗功能均是基于这个组件来实现效果。

Vue3引入公共组件

为了保持代码的整洁,把在main.js中引入的公共组件,分离到plugins.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)
}

这样在main.js中全局导入即可。

// 引入公用组件
import Plugins from './plugins'

const app = createApp(App)
app.use(Plugins)

// ...

小视频页布局模板

首页小视频页面整体分为顶部导航栏+视频区+底部标签栏三个部分。

<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实现小视频功能
 * @Time     andy by 2021-02
 * @About    Q:282310962  wx:xy190310
 */
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>

Vue3表单注册验证/倒计时控制


顶部警告提示信息使用v3popup组件实现功能。

<!-- //注册表单模板 -->
<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>
/**
 * @Desc     Vue3实现表单验证+60s倒计时
 * @Time     andy by 2021-02
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>

另外为了保证滑动的流畅性,开启lazy-render后大幅提升滑动性能。

短视频页面底部有一条迷你进度条展示。通过小视频时长和当前播放时间转换为百分比,然后通过css3 transition控制动画效果。

// 播放进度条
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)
}

另外,送礼物及充值功能是使用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">
                <div class="inner flex-c flex-col">
                    <img class="gtimg" src="/static/gift/gift-img22.png" />
                    <p class="gtlbl">鼓掌</p>
                    <p class="gtnum"><i class="iconfont icon-douzi"></i> 166</p>
                </div>
            </div> -->
            <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">
                    <img class="gtimg" :src="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>

好了,基于vue3+vite2构建仿抖音界面实战项目就分享到这里。希望大家能喜欢!✍🏻💪🏻
vue3.0+element-plus仿微信web版聊天室 | vue3 pc端聊天实例

本作品采用《CC 协议》,转载必须注明作者和本文链接
本文为原创文章,未经作者允许不得转载,欢迎大家一起交流 QQ(282310962) wx(xy190310)
本帖由系统于 3个月前 自动加精
讨论数量: 2

大佬,想找一个项目学习一些vue,您这个项目源码开源吗

2个月前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
文章
27
粉丝
36
喜欢
76
收藏
44
排名:374
访问:1.2 万
私信
所有博文
博客标签
react
1
angular
1
react仿微信
2
react实战开发
2
react+redux
1
react网页聊天
1
angular仿微信
1
angular聊天室
1
angular+node聊天
1
h5仿微信
1
仿微信语音
1
仿微信界面
1
RN弹窗
1
react-native自定义弹窗
1
react-native弹窗
1
React Native
1
reactNative仿微信
1
RN仿微信聊天
1
ReactNative朋友圈
1
uniapp仿微信
1
uniapp聊天室
2
uniapp聊天App
1
uni-app+vue实例
1
uni-app即时通讯
1
uniapp直播
1
uni-app仿抖音
1
uniapp仿陌陌
1
uni-app小视频
1
taro仿微信
1
taro聊天室
1
taro仿微信界面
1
taro+react聊天APP
1
taro即时通讯
1
electron+vue
1
electron-vue仿微信
1
electron聊天
1
electron实例
1
flutter实例
1
flutter仿微信
1
flutter+dart聊天
1
flutter聊天室
1
flutter聊天界面
1
vue自定义弹窗
1
vue全局对话框
1
vue长按弹出框
1
vue右键弹层
1
nuxt对话框
1
vue仿微信弹窗
1
vue仿探探
1
vue仿Tinder
1
vue卡片堆叠
1
vue翻牌滑动
1
nuxt仿探探
1
nuxt聊天室
1
nuxt仿微信
1
nuxt即时聊天
1
vue+nuxt聊天实例
1
nuxt.js朋友圈
1
vue.js自定义对话框
1
vue pc端弹窗
1
vue.js桌面端模态框
1
vue弹窗组件
1
vue仿layer
1
vue.js自定义滚动条
1
vue虚拟滚动条
1
vue美化滚动条
1
vue仿饿了么滚动条
1
Vue-Scrollbar
1
react.js弹窗示例
1
react桌面端弹框
1
react.js自定义对话框
1
react pc自定义弹窗
1
react全局弹框
1
vue3.0自定义组件
1
vue3弹框
1
vue3.x对话框
1
vue3.0弹窗
1
vue3.0实例
1
vue3.0聊天室
1
vue3.0仿微信
2
vue3聊天模板
1
vue3+vant3实战开发
1
vue3.x案例
1
vue3聊天实例
1
vue3.0仿QQ
1
vue3.x实战聊天
1
vue3网页聊天
1
vue3.0仿抖音app
1
vue3短视频
1
vue3.x小视频
1
vue3.0直播实例
1
vue3+vite2+vant3实战
1
vue3跨端开发
1
electron仿QQ
1
electron打包
1
electron聊天室
1
electron仿微信
1
electron仿抖音
1
electron短视频
1
electron直播
1
vite2+electron12
1
vite2+vue3.x+swiper
1
vue3+vite.js+vant3
1
vue3后台系统
1
Electron管理系统
1
vite2+electron后台
1
electron12权限管理
1
electron桌面端后台
1
vue3桌面管理
1
vite2+electron13
1
electron仿mac桌面
1
electron桌面管理
1
vite2桌面应用
1
uniapp短视频
1
uniapp仿抖音
1
uni-app直播
1