仿騰訊視頻-微信小程序

  騰訊視頻是一個讓咱們都喜好的視頻觀看平臺,用戶羣體也至關龐大。小編也很是喜歡使用騰訊視頻播放軟件,在娛樂的時間之中,也給本人來許多快樂。

前言

    在學習了小程序以後,爲了鞏固自身的學習知識和提升實戰能力。小編也很是的喜歡寫一個屬於本身的小程序,並且也發現有些人寫的視頻類小程序不是很細節,因此小編選了‘騰訊視頻’小程序,也開始走上一條「踩坑」的不歸路。寫下這邊文章也是爲了記念本身的痛苦之路,同時也但願給學習小程序的你帶來丁點幫助。javascript

項目部分gif演示





1. 前期準備

2. tabBar設計

    在設計小程序的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


               

3. 數據請求

    平常小程序開發過程當中基本時經過微信小程序開發工具提供的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();      }    })      },複製代碼

 4. 視頻搜索

      在小程序開發中,搜索功能是一個比較難實現的功能,尤爲涉及數據求以及動態化的實時搜索。下面小編進行一步一步搜索功能的解析


      搜索的樣式設計

      以頭部查詢爲例:(其餘樣式請見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;
}
複製代碼

     搜索功能部分

     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.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>複製代碼


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部分

<!-- 視頻菜單類型 -->  <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寬高保持一致而且隱藏起來,而後在pageonload中經過wx.createSelectorQuery()獲取標籤實際高度baseItemHeightpx單位):

swiper_height = baseItemHeight * child_num複製代碼

結果顯示本來的ip六、ipⅩ沒有問題,另外寬帶小於375的ip5上也ok,可是在大於375的設備上仍是出現空隙,好比ip的plus系列

     方案三:

   swiper底部有一個load標籤顯示「加載更多」,該標籤緊貼box其後,經過 wx.createSelectorQuery()來獲取 bottom,然而你會發現 bottom是標籤的 heighttop的和。計算底部空隙(暫時忽略「加載更多」標籤高度)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的滑動策略基本雷同。

6. 視頻播放

     

1. 主體設計

    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;  }複製代碼

2.切換電視劇劇集

                

    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中的屬性配置含義:

  • 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部分:

<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;}複製代碼

    4. 片花部分

   在設計片花部分,最主要的是採用什麼方式去解決,一次頁面渲染加載多個視頻問題,不少人直接用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值。其目的就是,爲去矯正你點擊某個圖片以後,視頻能夠在相應位置出現,也就達到點擊圖片播放的效果。

7. 短視頻

該模塊實現邏輯,基本與首頁差很少,直接看源碼便可複製代碼

                   
     實現基本思路:使用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;  }})複製代碼

 8. 個人

關於,個人部分實現基本內容是展現用戶頭像、姓名,顯示是否開通了會員,觀看歷史,個人看單和設置功能,因爲時間關係,小編只實現設置的部分功能

                 
       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}複製代碼

項目完整源碼:

github.com/hongdeyuan/…

9. 結語

      小編在寫該項目時,踩了很多的坑,這裏只寫出了幾個。雖然有些地方用框架的話會更方便,可是我以爲徒手寫項目本身的能力纔會獲得進階;最後,感謝你們來學習該文章,感謝大家的支持,歡迎各位來學習討論。
      若是你喜歡這篇文章或者能夠幫到你,不妨點個贊吧!

相關文章
相關標籤/搜索