騰訊視頻是一個讓咱們都喜好的視頻觀看平臺,用戶羣體也至關龐大。小編也很是喜歡使用騰訊視頻播放軟件,在娛樂的時間之中,也給本人來許多快樂。
在學習了小程序以後,爲了鞏固自身的學習知識和提升實戰能力。小編也很是的喜歡寫一個屬於本身的小程序,並且也發現有些人寫的視頻類小程序不是很細節,因此小編選了‘騰訊視頻’小程序,也開始走上一條「踩坑」的不歸路。寫下這邊文章也是爲了記念本身的痛苦之路,同時也但願給學習小程序的你帶來丁點幫助。javascript
在設計小程序的tabBar時,直接使用微信小程序官方提供給咱們的tabBar。那如何使用微信小程序提供的tabBar來設計騰訊視頻小程序的tabBar呢?css
a.首先,使用微信開發者工具(或者VScode)打開你新建的小程序項目,找到你的小程序中的app.json文件。在app.json文件中的pages項,新增以下配置:
html
b.接着,按(ctrl+s)進行保存。此時,微信開發者工具會幫你新建四個頁面文件夾,你在pages文件夾打開便可看到這四個文件夾。java
c.而後,在pages同級目錄下,新建images用來放置程序圖片資源。緊接着咱們去阿里巴巴矢量圖標庫搜索本身須要的tabBar對應的圖標,把它們下載放置到imgages中去。node
d.開始配置tabBar,找到你的小程序中的app.json文件。在app.json文件中的tabBar項,新增以下配置:git
"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.效果圖以下:github
平常小程序開發過程當中基本時經過微信小程序開發工具提供的wx.request來作數據請求,那麼怎麼可讓本身定義的數據庫呢?咱們這裏採用雲開發的微信提供的免費雲數據庫,作數據請求。在項目的cloudfunctions文件夾下新建幾個本身須要的雲函數請求響應的數據請求。
以獲取搜索建議爲例:
1. 雲函數部分:web
// 雲函數入口文件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.command let suggestVideo = 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) } }) let returnResult = []; for (let i = 0; i < suggestVideo.data.length; i++) { returnResult.push(suggestVideo.data[i]) } return returnResult.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(); } }) },複製代碼
以頭部查詢爲例:(其餘樣式請見github: 傳送門)json
在設計搜索欄的頭部時,咱們採用原生的樣式渲染方式,這樣你們也能夠理解其實現的原理,因此就不採用UI框架了,固然若是你想使用UI小編也能夠推薦你使用WeUI(微信原生視覺體驗一致的基礎樣式庫)。
很少說廢話啦,開始動手了。
1. 實現樣式設計思路:使用view包裹search的icon和input,讓包裹的view邊框角變成圓角,在使用行內塊級元素使其在一行顯示,並使用vertical-align: middle;居中對齊;
2. 搜索頭部基本結構
<!-- 搜索框 -->
<view class="page__bd">
<view class="searchsearch-bar">
<view class="search-bar__box">
<icon class="icon-search_in-box" type="search" size="14" color='#EDA8A3'></icon>
<input focus='true' type="text" class="search-bar__input" placeholder="請輸入片名、主演或導演" placeholder-style="font-size: 14px;color:#EDA8A3;padding-left:4px" value='{{inputValue}}' bindinput="getsearchKey" bindblur="routeSearchResPage" bindconfirm="searchover" />
<!-- 點擊×能夠清空正在輸入 -->
<view class="icon-clear" wx:if="{{share}}">
<icon type="clear" size="14" color='#EDA8A3' bindtap="clearInput"></icon>
</view>
</view>
<view class="search-bar__cancel-btn {{isCancel?'':'hide'}}" bindtap="cancel">取消</view>
</view>
</view>
複製代碼
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;
}
複製代碼
頁面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.command let suggestVideo = 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) } }) let returnResult = []; for (let i = 0; i < suggestVideo.data.length; i++) { returnResult.push(suggestVideo.data[i]) } return returnResult.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.command let serultVideo = 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) } }) let returnResult = []; for (let i = 0; i < serultVideo.data.length; i++) { returnResult.push(serultVideo.data[i]) } return returnResult.sort((a,b) => a.createTime < b.createTime ? 1 : -1)}複製代碼
特別注意:
搜索中有可能出現「抖動現象」,那麼如何解決該現象呢?此時,你須要採用debounce來解決,防止用戶屢次輸入抖動觸發搜索,從而致使屢次沒必要要的數據請求。
具體解決以下:
//獲取input文本而且實時搜索,動態隱藏組件 getsearchKey: function (e) { // console.log(e.detail.value) //打印出輸入框的值 let that = 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) { let timer let self = this return function (...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) { let history = wx.getStorageSync("history") || []; history.push(this.data.searchKey) wx.setStorageSync("history", history); } },
//每次顯示變更就去獲取緩存,給history,並for出來。 onShow: function () { this.setData({ history: wx.getStorageSync("history") || [] }) }
複製代碼
wxml對應部分
<!-- 搜索歷史 --> <view class="{{showView?'hot_keys':'header_view_hide'}}"> <view class="title history">搜索歷史</view> <icon type='clear' bindtap="clearHistory" class="title record" color="#DE655C"></icon> <view class='hot_key_box'> <view wx:for="{{history}}" wx:key="{{index}}"> <view class='hot_keys_box_item' data-value='{{item}}' data-index="{{index}}" bindtap="fill_value"> {{item}} </view> </view> </view> </view>複製代碼
首頁基本是結構的設計,以及輪播和菜單切換,主要時考驗咱們wxss的功底和js交互功底。
結構設計基本沒什麼大的難度,小編就很少廢話了,詳情見github項目(傳送門)。結果以下圖:
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部分:
<!-- 視頻菜單類型 --> <scroll-view class="shouye-menu {{scrollTop > 40 ? 'menu-fixed' : ''}}" scroll-x="{{true}}" scroll-y="{{false}}"> <block wx:for="{{moviesCategory}}" wx:key="{{item.id}}"> <text class="{{curentIndex === index ? 'active' : ''}}" data-id="{{item.id}}" data-index="{{index}}" bind:tap="switchTab" >{{item.name}}</text> </block> </scroll-view><!-- 切換主體部分 -->
<view class="content" style="height:{{ch}}rpx;"> <swiper class="swiper" bindchange='swiperChange' current='{{curentIndex}}'> <swiper-item> <scroll-view scroll-y class="scroll" wx:if="{{curentIndex == 0}}"> </scroll-view> </swiper-item>
</swiper>
<view>
複製代碼
2. js 部分
//改變swiper swiperChange: function(e) {//切換 if(e.detail.source == 'touch') { let curentIndex = 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的設備(ip六、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複製代碼
結果顯示本來的ip六、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 => { //轉爲rpx let ch = (750 / res.screenWidth) * res.windowHeight - 180; this.setData({ ch }) }, })
}複製代碼
式子中減 180 ,是小編本身調試的數據,具體減多少,能夠根據具體狀況而定。其實說白了,該方案的設計,基本是與better-scoll的滑動策略基本雷同。
a. 主體結構設計
<!-- pages/videoDetail/index.wxml --><view class="detailContainer"> <!-- <view class="detail_movice">{{showModalStatus == true ? 'stopScroll' : ''}} <video src="{{entitie.video}}" id="{{entitie.id}}" hidden="{{currentVid != entitie.id}}"></video> <image src="{{entitie.image}}" bind:tap="play" data-vid="{{entitie.id}}" hidden="{{currentVid == entitie.id}}"> <view class="label">{{entitie.duration}}</view> </image> </view> --> <view hidden="{{tvphide}}"> <txv-video vid="{{vid}}" class="{{detailOn ? '' : 'on'}}" width="{{width}}" height="{{height}}" playerid="txv0" autoplay="{{autoplay}}" controls="{{controls}}" title="{{title}}" defn="{{defn}}" vslideGesture="{{true}}" enablePlayGesture="{{true}}" playBtnPosition="center" bindstatechange="onStateChange" bindtimeupdate="onTimeUpdate" showProgress="{{showProgress1}}" show-progress="{{false}}" bindfullscreenchange="onFullScreenChange"></txv-video> </view> <!-- 介紹信息 --> <view class="introduce"> <view class="top"> <text class="introduce-top-title">{{entitie.header}}</text> <text class="introduce-top-text" bind:tap="showModal">簡介</text> </view> <view class="center"> <text class="introduce-center-text">8.6分·VIP·視頻·全36集·8.8億</text> </view> <view class="bottom"> <image class="ping" src="../../images/ping.png" lazy-load="{{true}}" bind:tap="" /> <image class="like" src="../../images/like.png" lazy-load="{{true}}" bind:tap="" /> <image class="share" src="../../images/share.png" lazy-load="{{true}}" /> <button data-item='1' plain='true' open-type='share' class='share'></button> </view> </view> <!-- 劇集 --> <view class="episode"> <view class="top"> <text class="episode-top-title">劇集</text> <text class="episode-top-text" bind:tap="showModal">每週一二三20點更新2集,會員多看6集</text> </view> <view class="center"> <block wx:for="{{episodes}}" wx:key="{{index}}"> <view class="gather" data-vid="{{item.vid}}" data-num="{{item.num}}" bind:tap="select"> <view class="{{vid == item.vid ? 'episode-num' : '' }}" data-vid="{{item.vid}}" data-num="{{item.num}}"> {{item.num}} </view> </view> </block> </view> </view> <!-- 精彩片花 --> <view class="clips"> <view class="clips-title"> <text class="clips-title-text">精彩片花</text> </view> <view class="mod_box mod_feeds_list"> <view class="mod_bd"> <view class="figure_box" wx:for="{{clips}}" wx:for-item="video" wx:for-index="index" wx:key="{{video.vid}}"> <view class="mod_poster" data-vid="{{video.vid}}" data-index="{{index}}" bindtap="onPicClick"> <image class="poster" src="{{video.img}}"></image> <!-- 標題、時間和播放按鈕 --> <view> <image class="play_icon" src="https://puui.qpic.cn/vupload/0/20181023_1540283106706_mem4262nz4.png/0"></image> <view class="time">{{video.time}}</view> <view class="toptitle two_row">{{video.title}}</view> </view> </view> </view> </view> <view wx:if="{{currVideo.vid}}" style="top:{{top+'px'}};" class="videoContainer"> <txv-video vid="{{currVideo.vid}}" playerid="tvp" autoplay="{{true}}" danmu-btn="{{true}}" width="{{'100%'}}" height="{{'194px'}}" bindcontentchange="onTvpContentChange" bindplay="onTvpPlay" bindended="onTvpEnd" bindpause="onTvpPause" bindstatechange="onTvpStateChanage" bindtimeupdate="onTvpTimeupdate" bindfullscreenchange="onFullScreenChange"></txv-video> <view class='pinList'> <footer></footer> </view> </view> </view> </view> <!-- 彈出框 --> <view animation="{{animationData}}" class="commodity_attr_box" wx:if="{{showModalStatus}}"> <view class="commodity_hide"> <text class="title">{{entitie.header}}</text> <view class="commodity_hide__on" bind:tap="hideModal"></view> </view> <view class="hight" catchtouchmove="stopScroll"> <scroll-view scroll-y class='hightDataView' style="height:{{ch}}rpx;"> <view class="top"> <view class="top-text">每週一二三20點更新2集,會員多看6集</view> <view class="top-descrese"> {{entitie.score}}分·VIP·{{entitie.type}}·全{{entitie.universe}}集·8.8億 </view> </view> <view class="center"> <scroll-view class="star-list" scroll-x="{{true}}" scroll-y="{{false}}"> <block wx:for="{{entitie.stars}}" wx:key="{{index}}"> <view class="item"> <image class="starImg" src="{{item.starImg}}" lazy-load="ture" /> <view class="name">{{item.name}}</view> </view> </block> </scroll-view> </view> <view class="bottom"> <view class="title">簡介</view> <view class="text">{{entitie.original_description}}</view> </view> </scroll-view> </view> </view></view>複製代碼
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')let currentVideo;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 => { //轉爲rpx let ch = (750 / res.screenWidth) * res.windowHeight -478; this.setData({ ch }) }, }) const id= options.id; console.log('id', id); let episode = episodes.find(function(item){ return item.id == id; }) let entitie = entities.find(function(item){ return item.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) { let dataset = e.currentTarget.dataset; this.currIndex=dataset.index this.setData({ "currVideo.vid":dataset.vid }) // console.log(this.data.currVideo) this.getTop() }, getTop(){ let query = this.createSelectorQuery(); query.selectViewport().scrollOffset(); query .selectAll(`.mod_poster`) .boundingClientRect() .exec(res => { let originTop = 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() { return false; }})複製代碼
c. 你可能會遇到的‘坑’
當你在設計簡介的時候,你會發現本身設計的彈出框的內部滑動事件與 當前頁的滑動事件一塊兒觸發了,那這是爲啥呢?仔細想一下,你會發現是冒泡和捕獲(詳解參考該博文)在搞鬼,相信寫過web項目的人對冒泡和捕獲很是的熟悉。那麼在小程序中也是有的,因此這裏你就須要瞭解滑動穿透這個東西了。那麼如何來解決這個問題吶?
解決辦法:在簡介中須要滑動的view中 加上catchtouchmove="stopScroll",而且在js中定義stopScroll方法並放回false便可解決。具體以下:
1. wxml:
<!-- 簡介彈出框 --> <view animation="{{animationData}}" class="commodity_attr_box" wx:if="{{showModalStatus}}"> <view class="commodity_hide"> <text class="title">{{entitie.header}}</text> <view class="commodity_hide__on" bind:tap="hideModal"></view> </view> <view class="hight" catchtouchmove="stopScroll"> <scroll-view scroll-y class='hightDataView' style="height:{{ch}}rpx;"> <view class="top"> <view class="top-text">每週一二三20點更新2集,會員多看6集</view> <view class="top-descrese"> {{entitie.score}}分·VIP·{{entitie.type}}·全{{entitie.universe}}集·8.8億 </view> </view> <view class="center"> <scroll-view class="star-list" scroll-x="{{true}}" scroll-y="{{false}}"> <block wx:for="{{entitie.stars}}" wx:key="{{index}}"> <view class="item"> <image class="starImg" src="{{item.starImg}}" lazy-load="ture" /> <view class="name">{{item.name}}</view> </view> </block> </scroll-view> </view> <view class="bottom"> <view class="title">簡介</view> <view class="text">{{entitie.original_description}}</view> </view> </scroll-view> </view> </view>複製代碼
2. js部分
// 默認阻止滾動 stopScroll() { return false; }複製代碼
a. 實現電視劇的劇集切換思路:拿到須要播放視頻的vid,將vid替換掉當前的vid,而後執行播放操做。
b.實現步驟:
1. 在.json文件中,配置騰訊視頻插件組件。以下:
{ "usingComponents": { "txv-video": "plugin://tencentvideo/video" }}複製代碼
2. 在wxml中使用,以下:
<view hidden="{{tvphide}}"> <txv-video
vid="{{vid}}"
class="{{detailOn ? '' : 'on'}}"
width="{{width}}"
height="{{height}}"
playerid="txv0"
autoplay="{{autoplay}}"
controls="{{controls}}"
title="{{title}}"
defn="{{defn}}"
vslideGesture="{{true}}"
enablePlayGesture="{{true}}"
playBtnPosition="center"
bindstatechange="onStateChange"
bindtimeupdate="onTimeUpdate"
showProgress="{{showProgress1}}"
show-progress="{{false}}"
bindfullscreenchange="onFullScreenChange"></txv-video></view>複製代碼
其中,在txv-video中的屬性配置含義:
其餘屬性見:騰訊視頻插件官方文檔
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(); }複製代碼
a. 簡介部分主要是wxcss的渲染,沒有什麼邏輯,須要注意的時,點擊下拉可使簡介下拉隱藏,並有下拉的過程出現。
b. 主要代碼以下:
1. wxml部分:
<view animation="{{animationData}}" class="commodity_attr_box" wx:if="{{showModalStatus}}"> <view class="commodity_hide"> <text class="title">{{entitie.header}}</text> <view class="commodity_hide__on" bind:tap="hideModal"></view> </view> <view class="hight" catchtouchmove="stopScroll"> <scroll-view scroll-y class='hightDataView' style="height:{{ch}}rpx;"> <view class="top"> <view class="top-text">每週一二三20點更新2集,會員多看6集</view> <view class="top-descrese"> {{entitie.score}}分·VIP·{{entitie.type}}·全{{entitie.universe}}集·8.8億 </view> </view> <view class="center"> <scroll-view class="star-list" scroll-x="{{true}}" scroll-y="{{false}}"> <block wx:for="{{entitie.stars}}" wx:key="{{index}}"> <view class="item"> <image class="starImg" src="{{item.starImg}}" lazy-load="ture" /> <view class="name">{{item.name}}</view> </view> </block> </scroll-view> </view> <view class="bottom"> <view class="title">簡介</view> <view class="text">{{entitie.original_description}}</view> </view> </scroll-view> </view> </view>複製代碼
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;}複製代碼
在設計片花部分,最主要的是採用什麼方式去解決,一次頁面渲染加載多個視頻問題,不少人直接用for循環放置,多個視頻video標籤;其實這是很是笨拙的辦法;小編在這作了一個比較高級的辦法,那就是:頁面放置的都是view來存放該視頻的vid,當點擊相應圖片時,觸發一個onPicClick事件,此時拿到須要播放的vid,並通知頁面我須要播放某個視頻了,請給我一個video去播放視頻;
此外,你須要注意的是,你這個video出現的位置,必須是你點擊的圖標位置,這樣就不會形成頁面圖片與視頻位置不符的問題了。並且,採用這種辦法,頁能夠減緩你的手機的cpu消耗,該辦法算是很是高明的手法了。下面來看下怎麼具體實現這種高明的手法吧。
a. wxml部分
<!-- 精彩片花 --> <view class="clips"> <view class="clips-title"> <text class="clips-title-text">精彩片花</text> </view> <view class="mod_box mod_feeds_list"> <view class="mod_bd"> <view class="figure_box" wx:for="{{clips}}" wx:for-item="video" wx:for-index="index" wx:key="{{video.vid}}"> <view class="mod_poster" data-vid="{{video.vid}}" data-index="{{index}}" bindtap="onPicClick"> <image class="poster" src="{{video.img}}"></image> <!-- 標題、時間和播放按鈕 --> <view> <image class="play_icon" src="https://puui.qpic.cn/vupload/0/20181023_1540283106706_mem4262nz4.png/0"></image> <view class="time">{{video.time}}</view> <view class="toptitle two_row">{{video.title}}</view> </view> </view> </view> </view> <view wx:if="{{currVideo.vid}}" style="top:{{top+'px'}};" class="videoContainer"> <txv-video vid="{{currVideo.vid}}" playerid="tvp" autoplay="{{true}}" danmu-btn="{{true}}" width="{{'100%'}}" height="{{'194px'}}" bindcontentchange="onTvpContentChange" bindplay="onTvpPlay" bindended="onTvpEnd" bindpause="onTvpPause" bindstatechange="onTvpStateChanage" bindtimeupdate="onTvpTimeupdate" bindfullscreenchange="onFullScreenChange"></txv-video> <view class='pinList'> <footer></footer> </view> </view> </view> </view>複製代碼
b.js交互部分
onPicClick(e) { let dataset = e.currentTarget.dataset; this.currIndex=dataset.index this.setData({ "currVideo.vid":dataset.vid }) // console.log(this.data.currVideo) this.getTop() }, getTop(){ let query = this.createSelectorQuery(); query.selectViewport().scrollOffset(); query .selectAll(`.mod_poster`) .boundingClientRect() .exec(res => { let originTop = 0; this.setData({ top: originTop + this.currIndex * 224.5 }) }); }複製代碼
c. 特別注意:
在getTop()方法中的邏輯,此處有些費解,爲啥要去設置top值。其目的就是,爲去矯正你點擊某個圖片以後,視頻能夠在相應位置出現,也就達到點擊圖片播放的效果。
該模塊實現邏輯,基本與首頁差很少,直接看源碼便可複製代碼
實現基本思路:使用swiper,scroll-view實現左右滑動菜單聯動,播放視頻思路與播放片花思路基本一致。
1. json配置
爲啥要配置,由於咱們這裏使用了騰訊視頻插件,以及本身定義的視頻尾部的組件,該尾部用於視頻分享操做,以及評論操做。配置以下:
{ "usingComponents": { "txv-video": "plugin://tencentvideo/video", "footer": "/components/footer/footer" }}複製代碼
2. wxml部分
<!-- pages/shortVideo/index.wxml --><scroll-view class="short-menu {{scrollTop > 40 ? 'menu-fixed' : ''}}" scroll-x="{{true}}" scroll-y="{{false}}"> <block wx:for="{{shortCategory}}" wx:key="{{item.id}}"> <view class="name {{curentIndex === index ? 'active' : ''}}" data-id="{{item.id}}" data-index="{{index}}" bind:tap="switchTab"> {{item.name}} </view> </block></scroll-view><view class="content" style="height:{{ch}}rpx;"> <swiper class="swiper" bindchange='swiperChange' current='{{curentIndex}}'> <block wx:for="{{videos}}" wx:key="{{index}}" wx:for-item="videoList"> <swiper-item class="{{index}}"> <scroll-view scroll-y class="scroll" wx:if="{{curentIndex == index}}"> <view class="mod_box mod_feeds_list"> <view class="mod_bd"> <view class="figure_box" wx:for="{{videoList.video}}" wx:for-item="video" wx:for-index="index" wx:key="{{video.vid}}"> <view class="mod_poster" data-vid="{{video.vid}}" data-index="{{index}}" bindtap="onPicClick"> <image class="poster" src="{{video.img}}"></image> <!-- 標題、時間和播放按鈕 --> <view> <image class="play_icon" src="https://puui.qpic.cn/vupload/0/20181023_1540283106706_mem4262nz4.png/0"></image> <view class="time">{{video.time}}</view> <view class="toptitle two_row">{{video.title}}</view> </view> </view> </view> </view> <view wx:if="{{currVideo.vid}}" style="top:{{top+'px'}};" class="videoContainer"> <txv-video vid="{{currVideo.vid}}" playerid="tvp" autoplay="{{true}}" danmu-btn="{{true}}" width="{{'100%'}}" height="{{'194px'}}" bindcontentchange="onTvpContentChange" bindplay="onTvpPlay" bindended="onTvpEnd" bindpause="onTvpPause" bindstatechange="onTvpStateChanage" bindtimeupdate="onTvpTimeupdate" bindfullscreenchange="onFullScreenChange"> </txv-video> <view class='pinList'> <footer></footer> </view> </view> </view> </scroll-view> </swiper-item> </block> </swiper></view>複製代碼
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') { let curentIndex = 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) { let dataset = e.currentTarget.dataset; this.currIndex=dataset.index this.setData({ "currVideo.vid":dataset.vid }) console.log(this.data.currVideo) this.getTop() }, getTop(){ let query = 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) let originTop = res[0].scrollTop; this.setData({ top: originTop + this.currIndex * 224.5 }) }); }, /** * 生命週期函數--監聽頁面加載 */ onLoad: function (options) { wx.getSystemInfo({ success: res => { //轉爲rpx let ch = (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() { return false; }})複製代碼
關於,個人部分實現基本內容是展現用戶頭像、姓名,顯示是否開通了會員,觀看歷史,個人看單和設置功能,因爲時間關係,小編只實現設置的部分功能
1.wxml部分
<!--miniprogram/pages/mine/mine.wxml--><view class="container"> <view class="header-image"> <image bindtap="bindViewTap" class="userinfo-avatar" src="{{userInfo.avatarUrl}}" mode="cover"></image> <text class="userinfo-nickname">{{userInfo.nickName}}</text> </view> <view class="vip"> <image src="../../images/no_vip.png" mode="widthFix"></image> <text class="h2">VIP暫未開通</text> <text class="bottom" bindtap="lookBans">請點此去開通</text> </view> <view class="history" bindtap="lookBans"> <image class="icon" src="../../images/history.png" mode="widthFix"></image> <text>觀看歷史</text><text class="fr"></text> </view> <view class="play-list history" bindtap="lookBans"> <image class="icon" src="../../images/view.png" mode="widthFix"></image> <text>個人看單</text><text class="fr"></text> </view> <view class="history" catchtap='navigatItem' data-url='/pages/setting/setting' data-open='true'> <image class="icon" src="../../images/settings.png" mode="widthFix"></image> <text>設置</text><text class="fr"></text> </view></view>複製代碼
2. js 部分
// miniprogram/pages/mine/mine.jsconst utils = require('../../utils/utils.js')//獲取應用實例const app = getApp()Page({ /** * 頁面的初始數據 */ data: { userInfo: {} }, navigatItem(e) { return utils.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源碼以下:
let navigatItem = (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}複製代碼
小編在寫該項目時,踩了很多的坑,這裏只寫出了幾個。雖然有些地方用框架的話會更方便,可是我以爲徒手寫項目本身的能力纔會獲得進階;最後,感謝你們來學習該文章,感謝大家的支持,歡迎各位來學習討論。
若是你喜歡這篇文章或者能夠幫到你,不妨點個贊吧!