仿腾讯视频-微信小程序

腾讯视频是一个让我们都喜爱的视频观看平台,用户群体也相当庞大。小编也非常喜欢使用腾讯视频播放软件,在娱乐的时间之中,也给本人来许多快乐。
【仿腾讯视频-微信小程序】前言
在学习了小程序之后,为了巩固自身的学习知识和提高实战能力。小编也非常的喜欢写一个属于自己的小程序,而且也发现有些人写的视频类小程序不是很细节,所以小编选了‘腾讯视频’小程序,也开始走上一条“踩坑”的不归路。写下这边文章也是为了纪念自己的痛苦之路,同时也希望给学习小程序的你带来丁点帮助。
项目部分gif演示
1. 前期准备
微信开发者工具(必须)
VScode编辑器(可选)
阿里巴巴矢量图标库-提供一些图标icon
腾讯视频-获取视频数据Vid
腾讯视频插件-配置
2. tabBar设计
在设计小程序的tabBar时,直接使用微信小程序官方提供给我们的tabBar。那如何使用微信小程序提供的tabBar来设计腾讯视频小程序的tabBar呢?
a.首先,使用微信开发者工具(或者VScode)打开你新建的小程序项目,找到你的小程序中的app.json文件。在app.json文件中的pages项,新增如下配置:
>need-to-insert-img
b.接着,按(ctrl+s)进行保存。此时,微信开发者工具会帮你新建四个页面文件夹,你在pages文件夹打开即可看到这四个文件夹。
c.然后,在pages同级目录下,新建images用来放置程序图片资源。紧接着我们去阿里巴巴矢量图标库搜索自己需要的tabBar对应的图标,把它们下载放置到imgages中去。
d.开始配置tabBar,找到你的小程序中的app.json文件。在app.json文件中的tabBar项,新增如下配置:
"tabBar": {"color":"#000000","selectedColor":"#FF4500","list": [{"pagePath":"pages/main/main","text":"首页","iconPath":"images/shouye.png","selectedIconPath":"images/shouye-action.png"},{"pagePath":"pages/shortVideo/index","text":"短视频","iconPath":"images/duanshiping.png","selectedIconPath":"images/duanshiping-action.png"},{"pagePath":"pages/brush/brush","text":"刷一刷","iconPath":"images/shuayishua.png","selectedIconPath":"images/shuayishua-action.png"},{"pagePath":"pages/mine/mine","text":"我的","iconPath":"images/mine.png","selectedIconPath":"images/mine-action.png"}]}复制代码
e.效果图如下:
>need-to-insert-img
3. 数据请求
日常小程序开发过程中基本时通过微信小程序开发工具提供的wx.request来做数据请求,那么怎么可以让自己定义的数据库呢?我们这里采用云开发的微信提供的免费云数据库,做数据请求。在项目的cloudfunctions文件夹下新建几个自己需要的云函数请求响应的数据请求。
以获取搜索建议为例:
1. 云函数部分:
// 云函数入口文件const cloud = require('wx-server-sdk')const env ='dhyuan'cloud.init()// 获取数据库句柄suggestVideoconst db = cloud.database({ env })// 云函数入口函数exports.main = async (event, context) => {// cloud.getWXContext()直接拿到用户信息console.log(event.key)// 查询建议的 模糊查询const _ = db.commandletsuggestVideo = await db.collection('suggestVideo').where(_.or([{keyword: db.RegExp({regexp:'.*'+ event.key,options:'i',})}])).get({success: res => {console.log(res)},fail: err => {console.log(err)}})letreturnResult = []; for(leti = 0; i < suggestVideo.data.length; i++) {returnResult.push(suggestVideo.data[i])}returnreturnResult.sort((a,b) => a.createTime < b.createTime ? 1 : -1)}复制代码
2. 搜索页中的 数据请求调用 与函数部分:
// 搜索建议searchSuggest() {const self = this; //展示标题栏的lodingwx.showNavigationBarLoading(); //调用云函数wx.cloud.callFunction({name:'search',data:{ key: self.data.searchKey },success(res){// console.log(res); self.setData({showvideoresult:true,searchsuggest: res.result})},fail(err){// console.log(err); self.setData({showvideoresult:false})},complete(){//让 loding消失wx.hideNavigationBarLoading(); }})},复制代码
4. 视频搜索
在小程序开发中,搜索功能是一个比较难实现的功能,尤其涉及数据求以及动态化的实时搜索。下面小编进行一步一步搜索功能的解析
搜索的样式设计
以头部查询为例:(其他样式请见github: 传送门)
在设计搜索栏的头部时,我们采用原生的样式渲染方式,这样大家也可以理解其实现的原理,所以就不采用UI框架了,当然如果你想使用UI小编也可以推荐你使用WeUI(微信原生视觉体验一致的基础样式库)。
不多说废话啦,开始动手了。
1.实现样式设计思路:使用view包裹search的icon和input,让包裹的view边框角变成圆角,在使用行内块级元素使其在一行显示,并使用vertical-align: middle; 居中对齐;
2.搜索头部基本结构
取消复制代码
3. 样式渲染
/* 搜索bar */.page__bd {position: fixed; top:0; width:100%; background-color:#FF4500; z-index:1; }.search-bar {width:100%; display: flex; background-color:#FF4500; border: 1pxsolid#DC4238; }.search-bar__box{vertical-align: middle; height: 65.52rpx; margin: 20rpx 10rpx 20rpx 25rpx; background-color:#DE655C; border-radius: 20rpx; width: 75%; padding-left: 30rpx; padding-right: 30rpx; display: inline-block; align-items: center; }.icon-search_in-box{width: 32.76rpx; height: 32.76rpx; vertical-align: middle; display: inline-block; }.icon-clear{width: 32.76rpx; height: 32px 0.76rpx; vertical-align: middle; display: inline-block; margin-left: 80rpx; }.search-bar__input{vertical-align: middle; display: inline-block; }.search-bar__cancel-btn {color:#ffffff; display: inline-block; font-size:32rpx; }复制代码
搜索功能部分
1. 实现思路:a. 关键字搜索建议:绑定input输入框使其每输入一个值触发关键字搜索建议操作,并展示给用户观看,此时展示你的搜索建议view设置z-index;b. 关键字搜索结果:当你输完关键字回车时,触发搜索结果操作,云函数去查询云数据库并放回相关数据;c.取消:当你点击取消时,此时小程序会放回到首页;d.搜索历史:当你每次输完关键字点击回车时,使用wx.setStorageSync保存数据到本地,当回到搜索主页时,从本次内存取出你查询过的关键字即可。
2. 实现关键字搜索建议
页面js求
searchResult() {// console.log(this.data.searchKey)const self = this; //展示标题栏的lodingwx.showNavigationBarLoading(); //调用云函数wx.cloud.callFunction({name:'searchResult',data:{ key: self.data.searchKey },success(res){// console.log(res); self.setData({showvideoresult:false,searchresult: res.result})},fail(err){// console.log(err); self.setData({showvideoresult:false})},complete(){//让 loding消失wx.hideNavigationBarLoading(); }})}复制代码
云函数:
// 云函数入口文件const cloud = require('wx-server-sdk')const env ='dhyuan'cloud.init()// 获取数据库句柄suggestVideoconst db = cloud.database({ env })// 云函数入口函数exports.main = async (event, context) => {// cloud.getWXContext()直接拿到用户信息console.log(event.key)// 查询建议的 模糊查询const _ = db.commandletsuggestVideo = await db.collection('suggestVideo').where(_.or([{keyword: db.RegExp({regexp:'.*'+ event.key,options:'i',})}])).get({success: res => {console.log(res)},fail: err => {console.log(err)}})letreturnResult = []; for(leti = 0; i < suggestVideo.data.length; i++) {returnResult.push(suggestVideo.data[i])}returnreturnResult.sort((a,b) => a.createTime < b.createTime ? 1 : -1)}复制代码
3. 关键字搜索结果
js请求
// 搜索结果searchResult() {// console.log(this.data.searchKey)const self = this; //展示标题栏的lodingwx.showNavigationBarLoading(); //调用云函数wx.cloud.callFunction({name:'searchResult',data:{ key: self.data.searchKey },success(res){// console.log(res); self.setData({showvideoresult:false,searchresult: res.result})},fail(err){// console.log(err); self.setData({showvideoresult:false})},complete(){//让 loding消失wx.hideNavigationBarLoading(); }})}复制代码
云函数
// 云函数入口文件const cloud = require('wx-server-sdk')const env ='dhyuan'cloud.init()// 获取数据库句柄suggestVideoconst db = cloud.database({ env })// 云函数入口函数exports.main = async (event, context) => {// cloud.getWXContext()直接拿到用户信息console.log(event.key)// 查询建议的 模糊查询const _ = db.commandletserultVideo = await db.collection('searchResult').where(_.or([{title: db.RegExp({regexp:'.*'+ event.key,options:'i',})},{artists: db.RegExp({regexp:'.*'+ event.key,options:'i',})}])).get({success: res => {console.log(res)},fail: err => {console.log(err)}})letreturnResult = []; for(leti = 0; i < serultVideo.data.length; i++) {returnResult.push(serultVideo.data[i])}returnreturnResult.sort((a,b) => a.createTime < b.createTime ? 1 : -1)}复制代码
特别注意:
搜索中有可能出现“抖动现象”,那么如何解决该现象呢?此时,你需要采用debounce来解决,防止用户多次输入抖动触发搜索,从而导致多次不必要的数据请求。
具体解决如下:
//获取input文本并且实时搜索,动态隐藏组件getsearchKey:function(e) {// console.log(e.detail.value) //打印出输入框的值letthat = this; if(e.detail.cursor != that.data.cursor) { //实时获取输入框的值that.setData({searchKey: e.detail.value})}if(e.value !="") { //组件的显示与隐藏that.setData({showView:false,share:true})}else{that.setData({showView:""})}if(e.detail.value !="") { //解决 如果输入框的值为空时,传值给搜索建议,会报错的bugthat.debounce(that.searchSuggest(), 300)}},// 去除输入抖动debounce (func, delay) {lettimerletself = thisreturnfunction(...args) {if(timer) {clearTimeout(timer)}timer =setTimeout(() => {func.apply(self, args)}, delay)}},复制代码
4. 取消
js操作
//实现取消功能,停止搜索,返回首页cancel:function() {wx.switchTab({url:'/pages/main/main'})},复制代码
5. 搜索历史
js操作
routeSearchResPage:function(e) {// console.log(e.detail.value)// 将数据存入本地if(this.data.searchKey) {lethistory= wx.getStorageSync("history") || []; history.push(this.data.searchKey)wx.setStorageSync("history",history); }},//每次显示变动就去获取缓存,给history,并for出来。onShow:function() {this.setData({history: wx.getStorageSync("history") || []})}复制代码
wxml对应部分
搜索历史{{item}}复制代码
5. 首页部分
首页基本是结构的设计,以及轮播和菜单切换,主要时考验我们wxss的功底和js交互功底。
1.样式结构设计
结构设计基本没什么大的难度,小编就不多废话了,详情见github项目(传送门)。结果如下图:
2.滑动菜单切换&轮播
a. 对于菜单的滑动切换,其实实现非常简单
在实现之前,你需要了解的几个标签:swiper,swiper-item,scroll-view; 滑块视图容器。swiper:其中只可放置swiper-item组件,否则会导致未定义的行为; scroll-view可滚动视图区域。使用竖向滚动时,需要给scroll-view一个固定高度,通过 WXSS 设置 height。组件属性的长度单位默认为px,2.4.0起支持传入单位(rpx/px)。
b. 菜单滑动切换实现思路:给swiper绑定一个bindchange='swiperChange'事件,每当用户滑动页面时,触发'swiperChange'事件,并且在js中定义一个数据变量为curentIndex,通过监听if(e.detail.source == 'touch')其中的touch事件,从而让curentIndex用来记录每次滑动切换的swiper-item,并通过wx:if="{{curentIndex == 0}}来判断当前的swiper-item是否显示,从而达到滑动切换菜单的效果。并且,菜单栏的index也与curentIndex进行判断,从而让指定的菜单高亮显示。
c. 实现过程
1. wxml部分
{{item.name}}复制代码
2. js 部分
//改变swiperswiperChange:function(e) {//切换if(e.detail.source =='touch') {letcurentIndex = e.detail.current; this.setData({curentIndex})}},switchTab(e){this.setData({curentIndex:e.currentTarget.dataset.index,toView: e.currentTarget.dataset.id})}复制代码
d. 你可能会踩的“坑”
在你使用 swiper 和scroll-view时,会出现swiper-item中的内容超出可视范围时,无法上下滑动问题。这是你要第一时间想到“swiper高度自适应”这个关键词。小编在这提供几种解决方式。
方案一:
swiper高度固定,swiper-item默认绝对定位且宽高100%,每个swiper-item中内容由固定高度的child组成,然后根据child数量动态计算swiper高度,初始方案(由于rpx针对屏幕宽度进行自适应,child_height使用rpx方便child正方形情况下自适应):
swiper_height = child_height * child_num
屏幕效果仅在宽度375的设备(ip6、ipⅩ)完美契合,其他设备都底部会出现多余空隙,并且在上拉加载过程中,随着内容增加,底部空隙也逐渐变大。
方案二:
swiper_height = child_height * child_num * ( window_width / 750 )复制代码
然后并无变化,我们可以看到child_height在不同宽度屏幕下,显示的宽高尺寸是不一样的(px单位),那就尝试使用box在各个屏幕的实际高度进行计算swiper高度,box的高度可以单独在页面中增加一个固定标签,该标签样式和box宽高保持一致并且隐藏起来,然后在page的onload中通过wx.createSelectorQuery()获取标签实际高度baseItemHeight(px单位):
swiper_height = baseItemHeight * child_num复制代码
结果显示原本的ip6、ipⅩ没有问题,另外宽带小于375的ip5上也ok,但是在大于375的设备上还是出现空隙,比如ip的plus系列
方案三:
swiper底部有一个load标签显示“加载更多”,该标签紧贴box其后,通过wx.createSelectorQuery()来获取bottom,然而你会发现bottom是标签的height加top的和。计算底部空隙(暂时忽略“加载更多”标签高度)space_height = swiper_height - load_top刚计算完可以看到在静止状态下,计算出space_height拿去修改swiper_height显示空隙刚好被清掉了,但是接着就发现在动过程中获取到的bottom是不固定的,也就是说数值可能不准确导致space_height计算错误,显示效果达不到要求
小编采用的是方案一
思路:给swiper一个外部view装载swiper,并给它设置style="height:{{ch}}rpx; ",这里的ch为js中的数据变量方便动态修改view的高度。并且在js中的钩子函数中的onLoad函数中编写如下代码:
onLoad:function(options) {wx.getSystemInfo({success: res => {//转为rpxletch = (750 / res.screenWidth) * res.windowHeight - 180; this.setData({ch})},})}复制代码
式子中减 180 ,是小编自己调试的数据,具体减多少,可以根据具体情况而定。其实说白了,该方案的设计,基本是与better-scoll的滑动策略基本雷同。
6. 视频播放
1. 主体设计
a. 主体结构设计
{{showModalStatus ==true?'stopScroll':''}}{{entitie.duration}}-->{{entitie.header}}简介8.6分·VIP·视频·全36集·8.8亿
剧集每周一二三20点更新2集,会员多看6集{{item.num}}精彩片花{{entitie.header}}每周一二三20点更新2集,会员多看6集{{entitie.score}}分·VIP·{{entitie.type}}·全{{entitie.universe}}集·8.8亿简介{{entitie.original_description}}复制代码
b. js交互
// pages/videoDetail/index.jsconst entities = require('../../data/entities.js')const txvContext = requirePlugin("tencentvideo")const config = require('../../modules/config')const episodes = require('../../data/episodes.js')letcurrentVideo; Page({/*** 页面的初始数据*/data: {entitie: null,id: null,entities,clips: null,currentVid:null,episodes: null,tvphide:false,vid: null,title:"电视剧",defn:"超清",changingvid:'',controls: !!config.get('controls'),autoplay: !!config.get('autoplay'),playState:'',showProgress1:true,width:"100%",height:"auto",showModalStatus:false,car:{},detailOn:true,ch: 0,currentIndex: 0,top: 0,currVideo:{}},play(event){const target = event.target; const currentVid = target.dataset.vid; if(this.data.currentVid!=null){currentVideo.pause(); }if(currentVid){currentVideo = wx.createVideoContext(`${currentVid}`); this.txvContext.pause(); currentVideo.play(); }this.setData({currentVid})},select(e){const target = e.target; const currentVid = target.dataset.vid; const num = target.dataset.num; console.log(currentVid, num); this.setData({vid: currentVid,clips: this.data.episodes[num-1].clips})this.txvContext = txvContext.getTxvContext('txv0'); this.txvContext.play(); },/*** 生命周期函数--监听页面加载*/onLoad:function(options) {//动态设置 详情的高度防止滑动失效wx.getSystemInfo({success: res => {//转为rpxletch = (750 / res.screenWidth) * res.windowHeight -478; this.setData({ch})},})const id= options.id; console.log('id', id); letepisode = episodes.find(function(item){returnitem.id == id; })letentitie = entities.find(function(item){returnitem.id == id; })this.setData({entitie})//改变page里面的datathis.setData({id: id,episodes: episode.episodes,vid: episode.episodes[0].vid,clips: episode.episodes[0].clips})// console.log('vid', this.data.vid); this.setData({controls: !!config.get('controls'),autoplay: !!config.get('autoplay')})this.txvContext = txvContext.getTxvContext('txv0'); this.txvContext.play(); this.videoContext = wx.createVideoContext('tvp'); },onTvpPlay:function() {// console.log('play')},onStateChange:function(e) {this.setData({playState: e.detail.newstate})},onTvpContentChange:function() {},onTimeUpdate:function(e) {},requestFullScreen:function() {this.txvContext.requestFullScreen(); },onFullScreenChange:function() {// console.log('onFullScreenChange!!!')},onTvpTimeupdate:function(){},onTvpPause:function() {},onTvpStateChanage:function() {},onPicClick(e) {letdataset = e.currentTarget.dataset; this.currIndex=dataset.indexthis.setData({"currVideo.vid":dataset.vid})// console.log(this.data.currVideo)this.getTop()},getTop(){letquery = this.createSelectorQuery(); query.selectViewport().scrollOffset(); query.selectAll(`.mod_poster`).boundingClientRect().exec(res => {letoriginTop = 0; this.setData({top: originTop + this.currIndex * 224.5})}); },/*** 生命周期函数--监听页面初次渲染完成*/onReady:function() {},/*** 生命周期函数--监听页面显示*/onShow:function() {},/*** 生命周期函数--监听页面隐藏*/onHide:function() {},/*** 生命周期函数--监听页面卸载*/onUnload:function() {},/*** 页面相关事件处理函数--监听用户下拉动作*/onPullDownRefresh:function() {},/*** 页面上拉触底事件的处理函数*/onReachBottom:function() {},/*** 用户点击右上角分享*/onShareAppMessage:function() {console.log('share success!')},//显示对话框showModal:function() {// 显示遮罩层var animation = wx.createAnimation({duration: 200,timingFunction:"linear",delay: 0})this.animation = animationanimation.translateY(300).step()this.setData({animationData: animation.export(),showModalStatus:true,detailOn:false})setTimeout(function() {animation.translateY(0).step()this.setData({animationData: animation.export()})}.bind(this), 200)},//隐藏对话框hideModal:function() {// 隐藏遮罩层var animation = wx.createAnimation({duration: 200,timingFunction:"linear",delay: 0})this.animation = animation; animation.translateY(300).step(); this.setData({animationData: animation.export(),})setTimeout(function() {animation.translateY(0).step()this.setData({animationData: animation.export(),showModalStatus:false,detailOn:true})}.bind(this), 200)},// 默认阻止滚动stopScroll() {returnfalse; }})复制代码
c. 你可能会遇到的‘坑’
当你在设计简介的时候,你会发现自己设计的弹出框的内部滑动事件与 当前页的滑动事件一起触发了,那这是为啥呢?仔细想一下,你会发现是冒泡和捕获(详解参考该博文)在搞鬼,相信写过web项目的人对冒泡和捕获非常的熟悉。那么在小程序中也是有的,所以这里你就需要了解滑动穿透这个东西了。那么如何来解决这个问题呐?
解决办法:在简介中需要滑动的view中 加上catchtouchmove="stopScroll",并且在js中定义stopScroll方法并放回false即可解决。具体如下:
1. wxml:
{{entitie.header}}每周一二三20点更新2集,会员多看6集{{entitie.score}}分·VIP·{{entitie.type}}·全{{entitie.universe}}集·8.8亿
简介{{entitie.original_description}}复制代码
2. js部分
// 默认阻止滚动stopScroll() {returnfalse; }复制代码
2.切换电视剧剧集
a. 实现电视剧的剧集切换思路:拿到需要播放视频的vid,将vid替换掉当前的vid,然后执行播放操作。
b.实现步骤:
1. 在.json文件中,配置腾讯视频插件组件。如下:
{"usingComponents": {"txv-video":"plugin://tencentvideo/video"}}复制代码
2. 在wxml中使用,如下:
复制代码
其中,在txv-video中的属性配置含义:
vid: 腾讯视频的vid,用于拿到该视频资源(必须)
playerid:playerid必须要全局唯一,可以设置为vid,否则导致视频播放错乱(必须)
autoplay:是否自动播放;true|false
controls: 是否显示控制栏(播放,暂停,全屏的按钮显示)
title:视频标题
defn:视频清晰度,默认auto,可选值:流畅,标清,高清,超清,蓝光,4K,杜比
其他属性见:腾讯视频插件官方文档
3. js交互
select(e){const target = e.target; const currentVid = target.dataset.vid; const num = target.dataset.num; console.log(currentVid, num); this.setData({vid: currentVid,clips: this.data.episodes[num-1].clips})this.txvContext = txvContext.getTxvContext('txv0'); this.txvContext.play(); }复制代码
3. 简介实现
a. 简介部分主要是wxcss的渲染,没有什么逻辑,需要注意的时,点击下拉可以使简介下拉隐藏,并有下拉的过程出现。
b. 主要代码如下:
1. wxml部分:
{{entitie.header}}每周一二三20点更新2集,会员多看6集{{entitie.score}}分·VIP·{{entitie.type}}·全{{entitie.universe}}集·8.8亿
简介{{entitie.original_description}}复制代码
2. wxss部分:
.commodity_attr_box {width: 100%; height: 100%; color:#fff; overflow: hidden; position: fixed; bottom: 0; top: 420rpx; left: 0; z-index: 998; background-color: #1f1e1e; padding-top: 20rpx; }.commodity_movableView{width: 100%; height: 2024rpx; }.commodity_hide{position: relative; height: 50rpx; }.commodity_hide .title{margin-left: 30rpx; font-size: 35rpx; line-height: 35rpx; font-weight: 40; }.commodity_hide .commodity_hide__on{width: 50rpx; height: 50rpx; position: absolute; display: inline-block; right: 20rpx; }.commodity_hide .commodity_hide__on::after{position: absolute; top: 10rpx; content: ''; color: #fff; width: 20rpx; height: 20rpx; border-top: 4rpx solid #ece3e3; border-right: 4rpx solid #ece3e3; -webkit-transform: rotate(135deg); transform: rotate(135deg); }.commodity_attr_box .hightDataView{width: 100%; }.commodity_attr_box .hightDataView .top{background-color:#1f1e1e; color: #fff; height: 140rpx; box-sizing: border-box; border-bottom: 4rpx solid #8b8989; }.commodity_attr_box .hightDataView .top .top-text{font-size: 12px; margin-top: 35rpx; margin-left: 30rpx; margin-right: 50rpx; color: #C0C0C0; line-height: 25px; }.commodity_attr_box .hightDataView .top .top-descrese{margin-left: 30rpx; font-size: 12px; line-height: 25px; color: #C0C0C0; }.commodity_attr_box .hightDataView .center{border-bottom: 4rpx solid #8b8989; }.commodity_attr_box .hightDataView .center .star-list {width: 100%; margin-top: 30rpx; margin-left: 20rpx; margin-bottom: 50rpx; white-space: nowrap; box-sizing: border-box; }.commodity_attr_box .hightDataView .center .star-list .item{text-align: center; display: inline-block; padding:4rpx; }.commodity_attr_box .hightDataView .center .star-list .item image{width: 80rpx; height: 80rpx; border-radius: 50%; margin: 10rpx; }.commodity_attr_box .hightDataView .center .star-list .item .name{font-size: 10px; font-weight: normal; }.commodity_attr_box .hightDataView .bottom{width: 100%; }.commodity_attr_box .hightDataView .bottom .title{margin-left: 30rpx; font-size: 35rpx; line-height: 35rpx; font-weight: 40; margin-top: 30rpx; }.commodity_attr_box .hightDataView .bottom .text{font-size: 12px; font-weight: normal; text-indent: 34rpx; margin-top: 20rpx; color: #C0C0C0; margin-left: 30rpx; }复制代码
4. 片花部分
在设计片花部分,最主要的是采用什么方式去解决,一次页面渲染加载多个视频问题,很多人直接用for循环放置,多个视频video标签;其实这是非常笨拙的办法;小编在这做了一个比较高级的办法,那就是:页面放置的都是view来存放该视频的vid,当点击相应图片时,触发一个onPicClick事件,此时拿到需要播放的vid,并通知页面我需要播放某个视频了,请给我一个video去播放视频;
此外,你需要注意的是,你这个video出现的位置,必须是你点击的图标位置,这样就不会造成页面图片与视频位置不符的问题了。而且,采用这种办法,页可以减缓你的手机的cpu消耗,该办法算是非常高明的手法了。下面来看下怎么具体实现这种高明的手法吧。
a. wxml部分
精彩片花
复制代码
b.js交互部分
onPicClick(e) {letdataset = e.currentTarget.dataset; this.currIndex=dataset.indexthis.setData({"currVideo.vid":dataset.vid})// console.log(this.data.currVideo)this.getTop()},getTop(){letquery = this.createSelectorQuery(); query.selectViewport().scrollOffset(); query.selectAll(`.mod_poster`).boundingClientRect().exec(res => {letoriginTop = 0; this.setData({top: originTop + this.currIndex * 224.5})}); }复制代码
c. 特别注意:
getTop()方法中的逻辑,此处有些费解,为啥要去设置top值。其目的就是,为去矫正你点击某个图片之后,视频可以在相应位置出现,也就达到点击图片播放的效果。
7. 短视频
该模块实现逻辑,基本与首页差不多,直接看源码即可复制代码
实现基本思路:使用swiper,scroll-view实现左右滑动菜单联动,播放视频思路与播放片花思路基本一致。
1. json配置
为啥要配置,因为我们这里使用了腾讯视频插件,以及自己定义的视频尾部的组件,该尾部用于视频分享操作,以及评论操作。配置如下:
{"usingComponents": {"txv-video":"plugin://tencentvideo/video","footer":"/components/footer/footer"}}复制代码
2. wxml部分
{{item.name}}
复制代码
3. js部分
// pages/shortVideo/index.jsconst config = require('../../modules/config')const txvContext = requirePlugin("tencentvideo"); const sysInfo =wx.getSystemInfoSync()const shortCategory = require('../../data/shortCategory.js')const videoUrl = require('../../data/videoUrl.js')Page({/*** 页面的初始数据*/data: {curentIndex: 0,shortCategory: shortCategory,videos: videoUrl,ch: 0,top: 0,currVideo:{}},//改变swiperswiperChange:function(e) {//切换if(e.detail.source =='touch') {letcurentIndex = e.detail.current; this.setData({curentIndex})}},switchTab(e){this.setData({curentIndex:e.currentTarget.dataset.index,toView: e.currentTarget.dataset.id})},onTvpTimeupdate:function(){},onTvpPlay:function() {},onTvpPause:function() {},onTvpContentChange:function() {},onTvpStateChanage:function() {},onPicClick(e) {letdataset = e.currentTarget.dataset; this.currIndex=dataset.indexthis.setData({"currVideo.vid":dataset.vid})console.log(this.data.currVideo)this.getTop()},getTop(){letquery = this.createSelectorQuery(); query.selectViewport().scrollOffset(); query.selectAll(`.mod_poster`).boundingClientRect().exec(res => {console.log(res)console.log(res[0].scrollTop, res[1][this.currIndex].top)letoriginTop = res[0].scrollTop; this.setData({top: originTop + this.currIndex * 224.5})}); },/*** 生命周期函数--监听页面加载*/onLoad:function(options) {wx.getSystemInfo({success: res => {//转为rpxletch = (750 / res.screenWidth) * res.windowHeight - 80; this.setData({ch})},})this.videoContext = wx.createVideoContext('tvp'); },/*** 生命周期函数--监听页面初次渲染完成*/onReady:function() {},/*** 生命周期函数--监听页面显示*/onShow:function() {},/*** 生命周期函数--监听页面隐藏*/onHide:function() {},/*** 生命周期函数--监听页面卸载*/onUnload:function() {},/*** 页面相关事件处理函数--监听用户下拉动作*/onPullDownRefresh:function() {},/*** 页面上拉触底事件的处理函数*/onReachBottom:function() {},/*** 用户点击右上角分享*/onShareAppMessage:function() {},// 默认阻止滚动stopScroll() {returnfalse; }})复制代码
8. 我的
关于,我的部分实现基本内容是展示用户头像、姓名,显示是否开通了会员,观看历史,我的看单和设置功能,由于时间关系,小编只实现设置的部分功能
1.wxml部分

复制代码
2. js 部分
// miniprogram/pages/mine/mine.jsconst utils = require('../../utils/utils.js')//获取应用实例const app = getApp()Page({/*** 页面的初始数据*/data: {userInfo: {}},navigatItem(e) {returnutils.navigatItem(e)},getUserInfo:function(e) {app.globalData.userInfo = e.detail.userInfothis.setData({userInfo: e.detail.userInfo})},lookBans:function() {const that = this; wx.showModal({content:'暂时未开发!',showCancel:false,confirmColor:'#FF4500',success(res) {}})},/*** 生命周期函数--监听页面加载*/onLoad:function(options) {if(app.globalData.userInfo) {this.setData({userInfo: app.globalData.userInfo})}else{// 在没有 open-type=getUserInfo 版本的兼容处理wx.getUserInfo({success: res => {app.globalData.userInfo = res.userInfo; console.log(res.userInfo)this.setData({userInfo: res.userInfo})}})}},/*** 生命周期函数--监听页面初次渲染完成*/onReady:function() {},/*** 生命周期函数--监听页面显示*/onShow:function() {},/*** 生命周期函数--监听页面隐藏*/onHide:function() {},/*** 生命周期函数--监听页面卸载*/onUnload:function() {},/*** 页面相关事件处理函数--监听用户下拉动作*/onPullDownRefresh:function() {},/*** 页面上拉触底事件的处理函数*/onReachBottom:function() {},/*** 用户点击右上角分享*/onShareAppMessage:function() {}})复制代码
3. 你需要注意的地方
在实现 设置功能部分时,这个小编在utils中写一个共有的 工具函数,用于页面跳转等操作。utils.js源码如下:
letnavigatItem = (e) => {const url = e.currentTarget.dataset.url ||'/pages/main/main'const open = e.currentTarget.dataset.openconst toUrl = () => {wx.navigateTo({url,})}if(open) {toUrl()}else{if(ifLogined()) {toUrl()}else{wx.navigateTo({url:'/pages/mine/mine'})}}}module.exports = {navigatItem}复制代码
项目完整源码:
github.com/hongdeyuan/…
9. 结语
小编在写该项目时,踩了不少的坑,这里只写出了几个。虽然有些地方用框架的话会更方便,但是我觉得徒手写项目自己的能力才会得到进阶;最后,感谢大家来学习该文章,感谢你们的支持,欢迎各位来学习讨论。
如果你喜欢这篇文章或者可以帮到你,不妨点个赞吧!

原文: https://juejin.im/post/5dd401c9f265da0bc8031459

    推荐阅读