基于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自定义组件来实现效果,支持自定义插槽内容。

  1. <navbar :back="false" bgcolor="transparent" transparent>
  2. <template v-slot:title>
  3. <div class="navbar__tab">
  4. ...
  5. </div>
  6. </template>
  7. <template v-slot:right><div><i class="iconfont icon-search"></i></div></template>
  8. </navbar>
  9.  
  10. <tabbar
  11. bgcolor="linear-gradient(to bottom, transparent, rgba(0,0,0,.6))"
  12. color="rgba(255,255,255,.6)"
  13. activeColor="#fff"
  14. fixed
  15. />

◆ vue3.x全局弹出层组件

项目中所有的弹框应用场景均是之前开发的一款vue3自定义组件v3popup来实现功能。

vue3版的自定义弹框组件,拥有20+种自定义参数配置,多种弹框类型及动画效果。

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

◆ vite.config.js配置文件

一些简单的vite2项目配置,可进行一些常用环境及alias路径别名设置。

  1. /**
  2. * Vite2项目配置
  3. */
  4.  
  5. import vue from '@vitejs/plugin-vue'
  6.  
  7. import path from 'path'
  8.  
  9. /**
  10. * @type {import('vite').UserConfig}
  11. */
  12. export default {
  13. plugins: [vue()],
  14.  
  15. build: {
  16. // 基本目录
  17. // base: '/',
  18.  
  19. /**
  20. * 输出文件目录
  21. * @default dist(默认)
  22. */
  23. // outDir: 'target',
  24. },
  25.  
  26. // 环境配置
  27. server: {
  28. // 自定义接口
  29. port: 3000,
  30.  
  31. // 是否自动浏览器打开
  32. open: false,
  33.  
  34. // 是否开启https
  35. https: false,
  36.  
  37. // 服务端渲染
  38. ssr: false,
  39.  
  40. // 代理配置
  41. proxy: {
  42. // ...
  43. }
  44. },
  45.  
  46. // 设置路径别名
  47. alias: {
  48. '@': path.resolve(__dirname, './src'),
  49. '@components': path.resolve(__dirname, './src/components'),
  50. '@views': path.resolve(__dirname, './src/views')
  51. }
  52. }

◆ 引入公共组件

让项目代码更加整洁,在plugins.js中配置一些公共组件,然后在main.js中引入即可。

  1. /**
  2. * 引入公共组件
  3. */
  4.  
  5. // 引入Vant3.x组件库
  6. import Vant from 'vant'
  7. import 'vant/lib/index.css'
  8.  
  9. // 引入Vue3.x移动端弹层组件
  10. import V3Popup from '@components/v3popup'
  11.  
  12. import NavBar from '@components/navBar.vue'
  13. import TabBar from '@components/tabBar.vue'
  14.  
  15. import Utils from './utils'
  16. import Storage from './storage'
  17.  
  18. const Plugins = (app) => {
  19. app.use(Vant)
  20. app.use(V3Popup)
  21.  
  22. // 注册公用组件
  23. app.component('navbar', NavBar)
  24. app.component('tabbar', TabBar)
  25.  
  26. app.provide('utils', Utils)
  27. app.provide('storage', Storage)
  28. }
  29.  
  30. export default Plugins

◆ vue3.x表单验证+60s倒计时

  1. <!-- //注册表单模板 -->
  2. <template>
  3. <div>
  4. <div class="vui__scrollview vui__scrollview-lgreg flex1">
  5. <div class="nt__lgregPanel">
  6. <div class="lgreg-header">
  7. <div class="slogan">
  8. <img class="logo" src="/static/logo.png" />
  9. <p class="text ff-gg">Vue3.0-DouYin</p>
  10. </div>
  11. <div class="forms">
  12. <form @submit.prevent="handleSubmit">
  13. <div class="item flexbox flex_alignc">
  14. <input class="iptxt flex1" type="text" v-model="formObj.tel" placeholder="请输入手机号" maxlength="11" />
  15. </div>
  16. <div class="item flexbox flex_alignc">
  17. <input class="iptxt flex1" type="password" v-model="formObj.pwd" placeholder="请输入密码" />
  18. </div>
  19. <div class="item flexbox flex_alignc">
  20. <input class="iptxt flex1" type="text" v-model="formObj.vcode" placeholder="验证码" />
  21. <button class="btn-getcode" @click.prevent="handleVcode" :disabled="disabled">{{vcodeText}}</button>
  22. </div>
  23. <div class="item btns">
  24. <button class="flex-c" type="submit"><i class="iconfont icon-go c-fff"></i></button>
  25. </div>
  26. <div class="item lgreg-lk">
  27. <router-link class="navigator" to="/login">已有账号,去登录</router-link>
  28. </div>
  29. </form>
  30. </div>
  31. </div>
  32. </div>
  33. </div>
  34. </div>
  35. </template>
  1. <script>
  2. import { reactive, toRefs, inject, getCurrentInstance } from 'vue'
  3. export default {
  4. components: {},
  5. setup() {
  6. const { ctx } = getCurrentInstance()
  7.  
  8. const v3popup = inject('v3popup')
  9.  
  10. const utils = inject('utils')
  11.  
  12. const formObj = reactive({})
  13. const data = reactive({
  14. vcodeText: '获取验证码',
  15. disabled: false,
  16. time: 0,
  17. })
  18.  
  19. const VTMsg = (content) => {
  20. v3popup({
  21. content: `<div style='text-align:center;'><i class='iconfont icon-error'></i> ${content}</div>`,
  22. popupStyle: 'background:#ffefe6;color:#fe2c55;',
  23. position: 'top',
  24. time: 2
  25. })
  26. }
  27.  
  28. const handleSubmit = () => {
  29. if(!formObj.tel){
  30. VTMsg('手机号不能为空!')
  31. }else if(!utils.checkTel(formObj.tel)){
  32. VTMsg('手机号格式不正确!')
  33. }else if(!formObj.pwd){
  34. VTMsg('密码不能为空!')
  35. }else if(!formObj.vcode){
  36. VTMsg('验证码不能为空!')
  37. }else{
  38. // ...
  39. }
  40. }
  41.  
  42. // 倒计时
  43. const handleVcode = () => {
  44. if(!formObj.tel) {
  45. VTMsg('手机号不能为空!')
  46. }else if(!utils.checkTel(formObj.tel)) {
  47. VTMsg('手机号格式不正确!')
  48. }else {
  49. data.time = 60
  50. data.disabled = true
  51. countDown()
  52. }
  53. }
  54. const countDown = () => {
  55. if(data.time > 0) {
  56. data.vcodeText = '获取验证码('+ data.time +')'
  57. data.time--
  58. setTimeout(countDown, 1000)
  59. }else{
  60. data.vcodeText = '获取验证码'
  61. data.time = 0
  62. data.disabled = false
  63. }
  64. }
  65.  
  66. return {
  67. formObj,
  68. ...toRefs(data),
  69. handleSubmit,
  70. handleVcode
  71. }
  72. }
  73. }
  74. </script>

◆ vue3.x实现小视频功能

小视频页面使用了有赞组件库中的swipe组件来实现滑动切换,开启lazy-render让滑动更加流畅。

  1. <div class="vui__swipeview">
  2. <!-- ///滑动切换区 -->
  3. <van-swipe ref="swipeHorizontalRef" :show-indicators="false" :loop="false" @change="handleSwipeHorizontal">
  4. <van-swipe-item v-for="(item,index) in videoLs" :key="index">
  5. <template v-if="item.category == 'nearby'">
  6. <div class="swipe__nearLs">
  7. ...
  8. </div>
  9. </template>
  10. <template v-if="item.category == 'recommend' || item.category == 'follow'">
  11. <van-swipe vertical lazy-render :show-indicators="false" :loop="false" @change="handleSwipeVertical">
  12. <van-swipe-item v-for="(item2, index2) in item.list" :key="index2">
  13. <!-- ///视频模块 -->
  14. <div class="swipe__video">
  15. <video class="vdplayer" :id="'vd-'+index+'-'+index2" loop preload="auto"
  16. :src="item2.src"
  17. :poster="item2.poster"
  18. webkit-playsinline="true"
  19. x5-video-player-type="h5-page"
  20. x5-video-player-fullscreen="true"
  21. playsinline
  22. @click="handleVideoClicked"
  23. >
  24. </video>
  25. <span v-show="!isPlay" class="btn__play" @click="handleVideoClicked"><i class="iconfont icon-bofang"></i></span>
  26. </div>
  27. <!-- ///信息模块 -->
  28. <div class="swipe__vdinfo flexbox flex-col">
  29. <div class="flexbox flex-alignb">
  30. <!-- ///底部信息栏 -->
  31. <div class="swipe__footbar flex1">
  32. <div v-if="item2.ads" class="item swipe__superlk ads" @click="handleOpenLink(item2)">
  33. <i class="iconfont icon-copylink fs-28"></i>查看详情<i class="iconfont icon-arrR fs-24"></i>
  34. </div>
  35. <div v-if="item2.collectionLs&&item2.collectionLs.length>0" class="item swipe__superlk">
  36. <i class="iconfont icon-copylink fs-24 mr-10"></i><div class="flex1">合集《小鬼当家》主演花絮</div><i class="iconfont icon-arrR fs-24"></i>
  37. </div>
  38. <div class="item uinfo flexbox flex-alignc">
  39. <router-link to="/friend/uhome"><img class="avatar" :src="item2.avatar" /></router-link>
  40. <router-link to="/friend/uhome"><em class="name">{{item2.author}}</em></router-link>
  41. <button class="btn vui__btn vui__btn-primary" :class="item2.isFollow ? 'isfollow' : ''" @click="handleIsFollow(item.category, index2)">{{item2.isFollow ? '已关注' : '关注'}}</button>
  42. </div>
  43. <div class="item at">@{{item2.author}}</div>
  44. <div v-if="item2.topic" class="item kw"><em v-for="(kw,idx) in item2.topic" :key="idx">#{{kw}}</em></div>
  45. <div class="item desc">{{item2.desc}}</div>
  46. </div>
  47. <!-- ///右侧工具栏 -->
  48. <div class="swipe__toolbar">
  49. <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>
  50. <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>
  51. <div class="item" @click="isShowReplyPopup=true"><i class="ico iconfont icon-liuyan"></i><p class="num">{{item2.replyNum}}</p></div>
  52. <div class="item" @click="isShowSharePopup=true"><i class="ico iconfont icon-fenxiang"></i><p class="num">{{item2.shareNum}}</p></div>
  53. </div>
  54. </div>
  55. </div>
  56. </van-swipe-item>
  57. </van-swipe>
  58. </template>
  59. </van-swipe-item>
  60. </van-swipe>
  61. <!-- ///底部进度条 -->
  62. <div class="swipe__progress"><i class="bar" :style="{'width': vdProgress+'%'}"></i></div>
  63. </div>
  1. <script>
  2. import { onMounted, onUnmounted, ref, reactive, toRefs, inject, nextTick } from 'vue'
  3.  
  4. import CmtEditor from '@components/cmtEditor.vue'
  5.  
  6. // ...
  7.  
  8. export default {
  9. components: {
  10. CmtEditor,
  11. },
  12. setup() {
  13. // 定时器
  14. const vdTimer = ref(null)
  15. const tapTimer = ref(null)
  16. const swipeHorizontalRef = ref(null)
  17.  
  18. const editorRef = ref(null)
  19.  
  20. const v3popup = inject('v3popup')
  21.  
  22. // ...
  23.  
  24. // 垂直切换页面事件
  25. const handleSwipeVertical = (index) => {
  26. if(data.activeNav == 0) {
  27. // 附近页
  28. data.activeOneIdx = index
  29. }else if(data.activeNav == 1) {
  30. // 关注页
  31. data.activeTwoIdx = index
  32. // console.log('关注页索引:' + index)
  33. }else if(data.activeNav == 2) {
  34. // 推荐页
  35. data.activeThreeIdx = index
  36. // console.log('推荐页索引:' + index)
  37. }
  38.  
  39. vdTimer.value && clearInterval(vdTimer.value)
  40. data.vdProgress = 0
  41. data.isPlay = false
  42. let video = getVideoContext()
  43. if(!video) return
  44. video.pause()
  45. // 重新开始
  46. video.currentTime = 0
  47.  
  48. data.activeSwipeIndex = index
  49.  
  50. // 自动播放下一个
  51. handlePlay()
  52. }
  53.  
  54. // 播放
  55. const handlePlay = () => {
  56. console.log('播放视频...')
  57.  
  58. let video = getVideoContext()
  59. if(!video) return
  60. video.play()
  61. data.isPlay = true
  62.  
  63. // 设置进度条
  64. vdTimer.value = setInterval(() => {
  65. handleProgress()
  66. }, 16)
  67. }
  68.  
  69. // 暂停
  70. const handlePause = () => {
  71. console.log('暂停视频...')
  72.  
  73. let video = getVideoContext()
  74. if(!video) return
  75. video.pause()
  76. data.isPlay = false
  77. vdTimer.value && clearInterval(vdTimer.value)
  78. }
  79.  
  80. // 视频点击事件(判断单/双击)
  81. const handleVideoClicked = () => {
  82. console.log('触发视频点击事件...')
  83.  
  84. tapTimer.value && clearTimeout(tapTimer.value)
  85. data.clickNum++
  86. tapTimer.value = setTimeout(() => {
  87. if(data.clickNum >= 2) {
  88. console.log('双击事件')
  89. }else {
  90. console.log('单击事件')
  91. if(data.isPlay) {
  92. handlePause()
  93. }else {
  94. handlePlay()
  95. }
  96. }
  97. data.clickNum = 0
  98. }, 300)
  99. }
  100.  
  101. // 播放进度条
  102. const handleProgress = () => {
  103. let video = getVideoContext()
  104. if(!video) return
  105. let curTime = video.currentTime.toFixed(1)
  106. let duration = video.duration.toFixed(1)
  107. data.vdProgress = parseInt((curTime / duration).toFixed(2) * 100)
  108. }
  109.  
  110. // ...
  111.  
  112. // 打开链接
  113. const handleOpenLink = (item) => {
  114. // 监听路由地址栈
  115. handlePopStateOpen()
  116.  
  117. data.isShowLinkPopup = true
  118. data.linkSrc = item.ads
  119. data.linkTitle = item.adstitle ? item.adstitle : '网址链接'
  120. }
  121.  
  122. return {
  123. ...toRefs(data),
  124. swipeHorizontalRef,
  125. editorRef,
  126.  
  127. handleTabNav,
  128. handleSwipeHorizontal,
  129. handleSwipeVertical,
  130. handlePlay,
  131. handlePause,
  132. handleVideoClicked,
  133.  
  134. // ...
  135. }
  136. }
  137. }
  138. </script>

至于项目中的聊天模块就不详细介绍了,之前有分享过一篇vue3.0开发移动端聊天实例项目,感兴趣的可以去看看哈~~

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

◆ vue3.x弹幕功能简单实现

直播页面在小视频页面功能基础上新增弹幕,滚动消息区、送礼物、充值弹窗等功能。

弹幕功能的简单实现,共有3条滚动路线。

  1. const data = reactive({
  2. // ...
  3.  
  4. // 弹幕队列
  5. idx: 2,
  6. dmLs: [
  7. ...
  8. ],
  9. // 正在执行的弹幕队列
  10. dmActiveLs: []
  11. })
  12.  
  13. onMounted(() => {
  14. // ...
  15.  
  16. // 装载弹幕
  17. setInterval(() => {
  18. starDanMu()
  19. }, 1500)
  20. })
  21.  
  22. const starDanMu = () => {
  23. let query = null
  24. if(!query) {
  25. query = data.dmLs.shift()
  26. }
  27. if(query) {
  28. query.row = data.idx
  29. data.idx = (data.idx % 3 + 1)
  30. data.dmActiveLs.push(query)
  31. }
  32. }
  1. <div class="lv__wrap-danmu">
  2. <div class="danmu__bx">
  3. <div class="danmu__ls" v-for="item in dmActiveLs" :key="item.id" :data-row="item.row" @animationend="dmAnimationEnd">
  4. <div class="item">
  5. <img class="avatar" :src="item.avatar" />
  6. <p class="name">{{item.name}}</p><p class="desc">{{item.desc}}</p>
  7. </div>
  8. </div>
  9. </div>
  10. </div>

OK,以上就是使用vue3.x+vite2开发仿抖音小视频/直播的一些分享,希望对大家有些帮助哈~~ ✍

Vue3.0短视频+直播|vue3+vite2+vant3仿抖音界面|vue3.x小视频实例的更多相关文章

  1. Vite2+Electron仿抖音|vite2.x+electron12+vant3短视频|直播|聊天

    整合vite2+electron12跨平台仿抖音电脑版实战Vite2-ElectronDouYin. 基于vite2.0+electron12+vant3+swiper6+v3popup等技术跨端仿制 ...

  2. iOS视频直播初窥:高仿<喵播APP>

    视频直播初窥 视频直播,可以分为 采集,前处理,编码,传输, 服务器处理,解码,渲染 采集: iOS系统因为软硬件种类不多, 硬件适配性比较好, 所以比较简单. 而Android端市面上机型众多, 要 ...

  3. 10分钟快速上车短视频风口:基于uniapp框架创建自己的仿抖音短视APP

    在今年也就是第48次发布的<中国互联网络发展状况统计报告>有这样一个数据,21年的上半年以来,我国我国网民规模达10.11亿,其中短视频用户达8.88亿.碎片化的生活场景下,短视频成为人们 ...

  4. 基于vue+uniapp直播项目|uni-app仿抖音/陌陌直播室

    一.项目简介 uni-liveShow是一个基于vue+uni-app技术开发的集小视频/IM聊天/直播等功能于一体的微直播项目.界面仿制抖音|火山小视频/陌陌直播,支持编译到多端(H5.小程序.Ap ...

  5. iOS多种刷新样式、音乐播放器、仿抖音视频、旅游App等源码

    iOS精选源码 企业级开源项目,模仿艺龙旅行App 3D立体相册,可以旋转的立方体 横竖屏切换工具,使用陀螺仪检测手机设备方向,锁屏状... Swift版Refresh(可以自定义多种样式)架构方面有 ...

  6. uni-app仿抖音APP短视频+直播+聊天实例|uniapp全屏滑动小视频+直播

    基于uniapp+uView-ui跨端H5+小程序+APP短视频|直播项目uni-ttLive. uni-ttLive一款全新基于uni-app技术开发的仿制抖音/快手短视频直播项目.支持全屏丝滑般上 ...

  7. Vue3.0网页版聊天|Vue3.x+ElementPlus仿微信/QQ界面|vue3聊天实例

    一.项目简介 基于vue3.x+vuex+vue-router+element-plus+v3layer+v3scroll等技术构建的仿微信web桌面端聊天实战项目Vue3-Webchat.基本上实现 ...

  8. iOS 仿抖音 视频裁剪

    1.最近做短视频拍摄.其中的裁剪界面要做得和抖音的视频裁剪效果一样 需求:  裁剪有一个最大裁剪时间.最小裁剪时间.左右拖动可以实时查看对应的视频画面.拖动进度条也能查看对应的画面 .拖动底部视图也能 ...

  9. 关于个人项目(臻美MV【仿抖音App】)滑动切换视频的分析(前端角度)

    我们知道你天天刷抖音的时候可以上滑切换视频,互不影响.那么我们站在前端的角度能否可以实现这种效果呢?这是我的个人项目:臻美MV 下面我是用Vue写的,现在我把它开源. Vue: 初始界面 <te ...

随机推荐

  1. Eclipse导入外部jar包的步骤

    (1)首先在项目的跟目录下先建一个名字为lib的文件夹,通常外部导入的jar包都放在这个文件夹下面. (2)将需要用到的jar包复制到lib文件夹下面. (3)在项目中导入jar包 右键项目,选择Bu ...

  2. Java8接口的默认方法

    项目实战 实现上图接口的实现类有很多,其中有些实现类已经在生成环境了,现在需要新增几个实现类,都需要有回调方法,所以在接口中添加了一个回调的默认方法,如果使用接口的普通方法就得改所有实现了接口的实现类 ...

  3. EasyPoi中@Excel注解中numFormat的使用

    需求说明:使用EasyPoi时导出文件中折扣字段是小数,被测试同学提了一个bug,需要转成百分数导出. 个人觉得应该转百分号只要在@Excel注解里面加个属性即可,但是在网上的easypoi教程中没有 ...

  4. C#扫盲篇(一):反射机制--情真意切的说

    在一线编码已有多年,积累了不少非常实用的技能,最近的更新会逐步的分享出来,希望能帮助到还有一丢丢喜欢.Net的朋友,当然这些都比较适合入门选手,虽然自己已是个精通抄代码的老猿,但技术造诣仍是渣渣. 犹 ...

  5. 我是如何在短期内快速掌握Dubbo的原理和源码的(纯干货)?

    写在前面 上周,在[Dubbo系列专题]中更新了两篇文章<冰河开始对Dubbo下手了!>和<俯瞰Dubbo全局,阅读源码前必须掌握这些!!>,收到了很多小伙伴的微信私聊消息,大 ...

  6. 知识图谱和neo4j的基本操作

    一.知识图谱的简介 1.知识图谱是什么 知识图谱本质上是语义网络(Semantic Network)的知识库 可以理解为一个关系图网络. 2.什么是图 图(Graph)是由节点(Vertex)和边(E ...

  7. P4292 [WC2010]重建计划 点分治+单调队列

    题目描述 题目传送门 分析 看到比值的形式就想到 \(01分数规划\),二分答案 设当前的值为 \(mids\) 如果存在\(\frac{\sum _{e \in S} v(e)}{|S|} \geq ...

  8. 配置Oracle数据库和监听随Linux系统自启动【转】

     配置Oracle数据库和监听随Linux系统自启动     在某些情况下需要在Linux操作系统上提供一种无人值守的随机启动Oracle的功能,目的也许仅仅是为了帮助那些对Oracle细节非常不关心 ...

  9. MQ for linux安装与卸载【转】

    MQ for linux安装与卸载[转] 一.安装步骤:1. 用root帐号登录系统2. MQ安装程序需将代码安装到目录/opt/mqm下,将数据保存到目录/var/mqm下,需确保相关目录下有足够的 ...

  10. 【Redis3.0.x】数据类型

    Redis3.0.x 数据类型 五大数据类型 String(字符串) string 是 redis 最基本的类型.可以理解成与 Memcached 一模一样的类型,一个 key 对应一个 value. ...