仿腾讯视频-微信小程序

  腾讯视频是一个让我们都喜爱的视频观看平台,用户群体也相当庞大。小编也非常喜欢使用腾讯视频播放软件,在娱乐的时间之中,也给本人来许多快乐。

前言

在学习了小程序之后,为了巩固自身的学习知识和提高实战能力。小编也非常的喜欢写一个属于自己的小程序,而且也发现有些人写的视频类小程序不是很细节,所以小编选了‘腾讯视频’小程序,也开始走上一条“踩坑”的不归路。写下这边文章也是为了纪念自己的痛苦之路,同时也希望给学习小程序的你带来丁点帮助。

项目部分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;    //展示标题栏的loding    wx.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: 1px  solid#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;    //展示标题栏的loding    wx.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;    //展示标题栏的loding    wx.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 !="") { //解决 如果输入框的值为空时,传值给搜索建议,会报错的bug      that.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 部分

//改变swiper  swiperChange: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里面的data    this.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.index    this.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 = animation    animation.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.index    this.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:{}  },  //改变swiper  swiperChange: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.index    this.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.userInfo    this.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.open  const 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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容