2020年快手获得了春晚冠名合作,今年则是抖音成为春晚独家红包互动合作伙伴。
春晚的赞助商已逐步由传统行业转向互联网企业。社交短视频、直播已经成为很多新一代人的互动交流方式,随着5G技术的日趋成熟,不久的将来视频直播更加的受欢迎!
vue3.0-douyin 基于
vite2+vue3.0+vuex4+vant3+v3popup
技术开发的一款仿抖音/快手短视频直播聊天
项目。实现了短视频滑动效果、聊天评论/弹幕/送礼物等功能。
框架技术
- 技术框架:vue3.0+vue-router+vuex4
- 组件库:vant3.x (有赞移动端vue3组件库)
- 弹层组件:v3popup (移动端vue3弹框组件)
- 字体图标:阿里iconfont图标
- 导航条+Tab栏:自定义顶部导航/底部标签栏组件
目录结构
vue3.0自定义mobile弹框组件
V3Popup 一款多功能的vue3.x移动端弹框组件。支持组件式+函数式
调用方式,超过6+
弹框类型、5+
动画效果及20+
自定义配置参数。
如果对其实现方式感兴趣,可以去看看之前的这篇分享文章。
vite.config.js项目配置
基于vite脚手架构建工具创建的项目,会有一个vite.config.js
配置文件。可进行一些简单的环境及路径别名配置。
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
文件,用来引入一些常用的公共组件。
// 引入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)}
首页布局模板
首页分为顶部navbar+视频区+底部tabbar三个部分。其中底部及底部组件开启transparent
fixed
属性,会镂空固定在视频区之上。
<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>
vue3.0表单注册验证 | 倒计时功能
<!-- //注册表单模板 -->
<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.0表单验证|60s倒计时
* @Time andy by 2021-02
* @About Q:282310962 wx:xy190310
*/
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.0短视频+直播模块
使用vant3
中的swipe
组件实现小视频_左右上下滑动_切换效果。
上下滑动切换视频,并且停止小视频播放。
// 垂直切换视频事件
const handleSwipeVertical = (index) => {
if(data.activeNav == 0) {
// 附近页
data.activeOneIdx = index
}else if(data.activeNav == 1) {
// 关注页
data.activeTwoIdx = index
}else if(data.activeNav == 2) {
// 推荐页
data.activeThreeIdx = 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 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 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)
}
视频区底部有一条迷你进度条展示。通过小视频时长和当前播放时间转换为百分比,然后通过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.0开发仿抖音/快手界面短视频就分享到这里。💪
最后贴上一个Vue3 pc端实例项目