vue+UniApp 仿抖音 App 小视频|uniapp 直播界面 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
如果想在 V2EX 获得更好的推广效果,欢迎了解 PRO 会员机制:
pro/about
xiaoyan2017

vue+UniApp 仿抖音 App 小视频|uniapp 直播界面

  •  
  •   xiaoyan2017 Nov 13, 2019 5724 views
    This topic created in 2357 days ago, the information mentioned may be changed or developed.

    项目介绍

    Uni 直播 uniLiveShow 是基于 uni-app 开发的多端小视频 /聊天 /直播室实例项目,之前就有使用vue+uniapp 仿微信即时聊天,这次直播项目用到了 vue+Nvue+uniapp+vuex+swiper 等技术实现。并且小视频及直播页面均可类似抖音一样上下滑动切换,有播放、暂停、点赞、评论、商品等功能。

    多端预览图

    技术实现

    • 编辑器+技术:HBuilderX2.3.9 + vue/NVue/uniapp/vuex
    • iconfont 图标:阿里字体图标库
    • 自定义导航栏 + 底部 Tabbar
    • 弹窗组件:uniPop ( uni-app 封装自定义 Modal 弹窗)
    • 测试环境:H5 端 /微信小程序 /App 端 /真机
     /** * @tpl uniapp 主入口页面 * @about Q:282310962 wx:xy190310 */ import Vue from 'vue' import App from './App' // >>>引入 css import './assets/fonts/iconfont.css' import './assets/css/reset.css' import './assets/css/layout.css' // >>>引入状态管理 import store from './store' Vue.prototype.$store = store // >>>引入公共组件 import headerBar from './components/header/header.vue' import tabBar from './components/tabbar/tabbar.vue' Vue.component('header-bar', headerBar) Vue.component('tab-bar', tabBar) // >>>引入 uniPop 弹窗组件 import uniPop from './components/uniPop/uniPop.vue' Vue.component('uni-pop', uniPop) Vue.config.productiOnTip= false App.mpType = 'app' const app = new Vue({ ...App }) app.$mount() 

    项目中的聊天部分,可参看这篇:uniapp 聊天室 App|vue+uniapp 仿微信聊天界面|仿微信朋友圈

    uniapp 仿抖音实现

    uni-app+nvue 技术实现仿抖音界面滑动效果,且有点赞、评论及商品等功能,可以单击、双击判断。

    <swiper :indicator-dots="false" :duration="200" :vertical="true" :current="videoIndex" @change="handleSlider" style="height: 100%;"> <block v-for="(item,index) in vlist" :key="index"> <swiper-item> <view class="uni_vdplayer"> <video :id="'myVideo' + index" :ref="'myVideo' + index" class="player-video" :src="item.src" :cOntrols="false" :loop="true" :show-center-play-btn="false" objectFit="fill"> </video> <!-- 中间播放按钮 --> <view class="vd-cover flexbox" @click="handleClicked(index)"><text v-if="!isPlay" class="iconfont icon-bofang"></text></view> <!-- 底部信息 --> <view class="vd-footToolbar flexbox flex_alignb"> <view class="vd-info flex1"> <view class="item at"> <view class="kw" v-for="(kwItem,kwIndex) in item.keyword" :key="kwIndex"><text class="bold fs_18 mr_5">#</text> {{kwItem}}lt;/view> </view> <view class="item subtext">{{item.subtitle}}</view> <view class="item uinfo flexbox flex_alignc"> <image class="avator" :src="item.avator" mode="aspectFill" /><text class="name">{{item.author}}</text> <text class="btn-attention bg_linear1" :class="item.attention ? 'on' : ''" @tap="handleAttention(index)">{{item.attention ? '已关注' : '关注'}}</text> </view> <view class="item reply" @tap="handleVideoComment"><text class="iconfont icon-pinglun mr_5"></text> 写评论...</view> </view> <view class="vd-sidebar"> <view v-if="item.cart" class="ls cart flexbox bg_linear3" @tap="handleVideoCart(index)"><text class="iconfont icon-cart"></text></view> <view class="ls" @tap="handleIsLike(index)"><text class="iconfont icon-like" :class="item.islike ? 'like' : ''"></text><text class="num">{{ item.likeNum+(item.islike ? 1: 0) }}</text></view> <view class="ls" @tap="handleVideoComment"><text class="iconfont icon-liuyan"></text><text class="num">{{item.replyNum}}</text></view> <view class="ls"><text class="iconfont icon-share"></text><text class="num">{{item.shareNum}}</text></view> </view> </view> </view> </swiper-item> </block> </swiper> 

    mock 模拟的小视频数据

    /** * @desc 小视频 JSON 数据 */ module.exports = [ { id: 1, avator: '/static/uimg/u__chat_img1.jpg', poster: '/static/placeholder/video-img4.jpg', src: '/static/placeholder/video.mp4', author: '猪猪佩奇', subtitle: '稻城亚丁-人间绝美景色', keyword: ['美好回忆', '旅游圣地'], playNum: 3172, likeNum: 2518, replyNum: 292, shareNum: 107, islike: false, attention: false, cart: [ { name: '同款冬枣', image: '/static/placeholder/cart-img1.jpg', price: 9.90 }, { name: '10 斤装爆甜冰糖心红富士', image: '/static/placeholder/cart-img2.jpg', price: 9.90 }, { name: '红心猕猴桃 单果 40-70 克', image: '/static/placeholder/cart-img3.jpg', price: 10.0 } ] }, { id: 2, avator: '/static/uimg/u__chat_img12.jpg', poster: '/static/placeholder/video-img0.jpg', src: 'https://txmov2.a.yximgs.com/bs2/newWatermark/MTg3NDYzOTY3MjM_zh_3.mp4', author: 'Alisa', subtitle: '不要在乎别人的流言蜚语', keyword: ['经典老歌'], playNum: 9432, likeNum: 5627, replyNum: 1285, shareNum: 638, islike: true, attention: true, cart: '' }, { id: 3, avator: '/static/uimg/u__chat_img5.jpg', poster: '/static/placeholder/video-img2.jpg', src: 'https://txmov2.a.yximgs.com/bs2/newWatermark/MTY3NTU3MzYzMTQ_zh_4.mp4', author: '往后余生都是你', subtitle: '能不能给我一首歌的时间,让你拾起从前的快乐', keyword: '', playNum: 7268, likeNum: 3438, replyNum: 1105, shareNum: 327, islike: false, attention: false, cart: [ { name: 'YCID 施蒂蓝玫瑰凝养柔滑唇膏', image: 'https://cbu01.alicdn.com/img/ibank/2019/218/182/12384281812_1493014487.jpg', price: 7.70 }, { name: '玛可安迪新款抖音网红推荐口红', image: 'https://cbu01.alicdn.com/img/ibank/2019/285/249/10457942582_1068990292.jpg', price: 19.9 }, ] }, ... ] 

    小视频上下滑动切换、播放、暂停,商品及评论功能

    <script> const videoJson = require('./mock-video.js') // 引入商品广告、评论 import videoCart from '@/components/cp-video/cart.vue' import videoComment from '@/components/cp-video/comment' let timer = null export default { data() { return { videoIndex: 0, vlist: videoJson, isPlay: true, //当前视频是否播放中 clickNum: 0, //记录点击次数 } }, components: { videoCart, videoComment }, onLoad(option) { this.videoIndex = parseInt(option.index) }, onReady() { this.init() }, methods: { init() { this.videoCOntextList= [] for(var i = 0; i < this.vlist.length; i++) { // this.videoContextList.push(this.$refs['myVideo' + i][0]) this.videoContextList.push(uni.createVideoContext('myVideo' + i, this)); } setTimeout(() => { this.play(this.videoIndex) }, 200) }, // 滑动切换 handleSlider(e) { let curIndex = e.detail.current if(this.videoIndex >= 0){ this.videoContextList[this.videoIndex].pause() this.videoContextList[this.videoIndex].seek(0) this.isPlay = false } if(curIndex === this.videoIndex + 1) { this.videoContextList[this.videoIndex + 1].play() this.isPlay = true }else if(curIndex === this.videoIndex - 1) { this.videoContextList[this.videoIndex - 1].play() this.isPlay = true } this.videoIndex = curIndex }, // 播放 play(index) { this.videoContextList[index].play() this.isPlay = true }, // 暂停 pause(index) { this.videoContextList[index].pause() this.isPlay = false }, // 点击视频事件 handleClicked(index) { if(timer){ clearTimeout(timer) } this.clickNum++ timer = setTimeout(() => { if(this.clickNum >= 2){ console.log('双击视频') }else{ console.log('单击视频') if(this.isPlay){ this.pause(index) }else{ this.play(index) } } this.clickNum = 0 }, 300) }, // 喜欢 handleIsLike(index){ let vlist = this.vlist vlist[index].islike =! vlist[index].islike this.vlist = vlist }, // 显示评论 handleVideoComment() { this.$refs.videoComment.show() }, // 显示购物车 handleVideoCart(index) { this.$refs.videoCart.show(index) }, } } </script> 

    如果在开发时遇到 video 不能覆盖的问题,可改 vue 页面为 nvue,不过需要注意 nvue 页面 css 写法。 最后分享个:react+react-redux 仿微信 web 版聊天|网页版聊天室

    作者:xiaoyan2017
    链接: https://juejin.im/post/5dc97bfef265da4d026271b5
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    5 replies    2019-11-13 15:06:55 +08:00
    yueshang1
        1
    yueshang1  
       Nov 13, 2019
    差评,没有源码分享
    yuwangG
        2
    yuwangG  
       Nov 13, 2019
    差评,没有源码分享
    misty8873
        3
    misty8873  
       Nov 13, 2019
    差评,没有源码分享
    shede333
        4
    shede333  
       Nov 13, 2019
    差评,没有源码分享
    dfourc
        5
    dfourc  
       Nov 13, 2019
    这 B 是个机器人吧
    About     Help     Advertise     Blog     API     FAQ     Solana     5581 Online   Highest 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 50ms UTC 01:33 PVG 09:33 LAX 18:33 JFK 21:33
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86