Tencent Video is a video watching platform that we all love, and its user base is quite large. Xiaobian also very like to use Tencent video player software, in the time of entertainment, also give me a lot of happiness.

preface

After learning the small program, in order to consolidate their own learning knowledge and improve the actual combat ability. Xiaobian is also very like to write a small program of their own, but also found that some people write video small program is not very detailed, so the small editor selected the ‘Tencent video’ small program, also began to step on a “pit” road of no return. Write this article is to commemorate their own painful road, but also hope to learn small program you bring little help.

Project section GIF demo








1. Prepare

  • Wechat Developer Tools (required)
  • VScode editor (optional)
  • Alibaba Vector Icon Library – provides some icon ICONS
  • Tencent Video – Get video data Vid
  • Tencent Video plug-in – configuration

2. The tabBar design

When designing tabBar of small program, we directly use tabBar provided by wechat small program official. That how to use wechat small program provided tabBar to design Tencent video small program tabBar?

A. First, use the wechat developer tool (or VScode) to open your new applet project and find the app.json file in your applet. Add the following configuration to the Pages item in the app.json file:

                 

    

B. Press (CTRL + S) to save. At this point, wechat developer tools will help you create four new page folders, you can open in the Pages folder to see the four folders.

C. Then, in the Pages directory, create images to place the program’s image resources. Then we went to Alibaba vector icon library to search for tabBar corresponding ICONS we needed, and downloaded them into imgages.

D. Start configuring tabBar by finding the app.json file in your applet. Add the following configuration to tabBar in the app.json file:

"tabBar": {    "color": "# 000000"."selectedColor": "#FF4500"."list": [{"pagePath": "pages/main/main"."text": "Home page"."iconPath": "images/shouye.png"."selectedIconPath": "images/shouye-action.png"      },      {        "pagePath": "pages/shortVideo/index"."text": "Short video"."iconPath": "images/duanshiping.png"."selectedIconPath": "images/duanshiping-action.png"      },      {        "pagePath": "pages/brush/brush"."text": "Brush it."."iconPath": "images/shuayishua.png"."selectedIconPath": "images/shuayishua-action.png"      },      {        "pagePath": "pages/mine/mine"."text": "I"."iconPath": "images/mine.png"."selectedIconPath": "images/mine-action.png"}}]Copy the code

E. The effect picture is as follows:


               

3. Data request

The basic daily small program development process through the micro channel small program development tools provided by wx.request to do data requests, so how can let their own definition of the database? We use the free cloud database provided by wechat, which is developed by cloud, to make data requests. In the project’s cloudfunctions folder, create a few new data requests that the cloudfunctions need to respond to. Take obtaining search suggestions as an example: 1. Cloud function:

// cloud function entry file const cloud = require('wx-server-sdk')const env = 'dhyuan'SuggestVideoconst db = suggest.database ({env})// cloud function entry function exports.main = async (event, function, function) Context) => {// cloud.getwxContext () gets the user information console.log(event.key) const _ = db.mandlet 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)}Copy the code

2. Data request calls and functions in the search page:

// Search for suggestionssearchSuggest() { const self = this; / / display the title bar of loding wx. ShowNavigationBarLoading (); // Call the cloud function 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() {/ / to lose the loding wx hideNavigationBarLoading (); }})},Copy the code

4. Video search

In the small program development, search function is a relatively difficult function to implement, especially involving data search and dynamic real-time search. The following small series of step by step search function analysis


Search style design

Take header queries for example: (See Github: Portal for other styles)

In the design of the head of the search bar, we use native style rendering, so that we can understand the principle of its implementation, so we do not use the UI framework, of course, if you want to use UI xiaobian can also recommend you use WeUI(wechat native visual experience consistent basic style library). So without further ado, let’s get started.

                            

1. Implementation style design ideas: use view to wrap the icon and input of search, make the wrapped view border Angle become rounded, use the block level element inside the line to display it in a line, and use vertical-align: middle; Center alignment;

2. Search the basic structure of the header

<! -- Search box --> <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="Please enter title, lead actor or director" placeholder-style="font-size: 14px; color:#EDA8A3; padding-left:4px" value='{{inputValue}}' bindinput="getsearchKey" bindblur="routeSearchResPage" bindconfirm="searchover"/ > <! -- Click × to clear the input --> <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>Copy the code

3. Style rendering

/* Search 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.52 the RPX; 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.76 the RPX; Height: 32.76 the RPX; vertical-align: middle; display: inline-block; }. Icon - the clear {width: 32.76 the RPX; Height: 0.76 the RPX 32 p; 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;
}
Copy the code

Search function section

1. Implementation idea: a. Keyword search Suggestion: Bind the input box to trigger every input value
Keyword search suggestionsOperation, and show the user to watch, this time show your search suggestion view set z-index; B. Keyword search results: When you finish typing the keyword
enterWhen the trigger
The search resultsOperation, cloud function to query the cloud database and put back the relevant data; C. Cancel: When you click
cancel“, the small program will be put back to the home page; D. Search history: use when you enter the keyword every time
wx.setStorageSyncSave the data locally, and when you return to the search home page, retrieve the keywords you have queried from the memory.


2. Implement keyword search suggestions


                       

Js page o

searchResult() { // console.log(this.data.searchKey) const self = this; / / display the title bar of loding wx. ShowNavigationBarLoading (); // Call the cloud function 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() {/ / to lose the loding wx hideNavigationBarLoading (); }})}Copy the code

Cloud function:

// cloud function entry file const cloud = require('wx-server-sdk')const env = 'dhyuan'SuggestVideoconst db = suggest.database ({env})// cloud function entry function exports.main = async (event, function, function) Context) => {// cloud.getwxContext () gets the user information console.log(event.key) const _ = db.mandlet 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)}Copy the code

3. Keyword search results

                       

Js request

// Search resultssearchResult() { // console.log(this.data.searchKey) const self = this; / / display the title bar of loding wx. ShowNavigationBarLoading (); // Call the cloud function 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() {/ / to lose the loding wx hideNavigationBarLoading (); }})}Copy the code

Cloud function

// cloud function entry file const cloud = require('wx-server-sdk')const env = 'dhyuan'SuggestVideoconst db = suggest.database ({env})// cloud function entry function exports.main = async (event, function, function) Context) => {// cloud.getwxContext () gets the user information console.log(event.key) const _ = db.mandlet 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)}Copy the code

Special attention:

There may be “jitter” in search, so how to solve this phenomenon? In this case, you need to use Debounce to prevent multiple searches triggered by jitter from the user, resulting in multiple unnecessary data requests.

Specific solutions are as follows:

// Get the input text and search in real time, dynamically hide the component getsearchKey:function(e) {// console.log(e.daile.value) // Prints out the value of the input boxlet that = this;    if(e.detail.cursor ! SetData ({searchKey: e.daile.value})} = that.data.cursor) {setData({searchKey: e.daile.value})}if(e.value ! =""SetData ({showView:false,        share: true})}else {      that.setData({        showView: ""})}if(e.detail.value ! ="") {// Solve the search suggestion bug that.debounce(that.searchSuggest(), 300)}}, // remove the input debounce(func, delay) {let timer    let self = this    return function(... args) {if (timer) {        clearTimeout(timer)      }      timer = setTimeout(() => {        func.apply(self, args)      }, delay)    }  },Copy the code

4. Cancel the JS operation

// Implement the cancel function, stop the search, return to the homepage cancel:function () {    wx.switchTab({      url: '/pages/main/main'})},Copy the code

5. Search for history




Js operation

routeSearchResPage: function(e) {// console.log(e.daile.value) // Store data locallyif (this.data.searchKey) {      let history = wx.getStorageSync("history") | | []; history.push(this.data.searchKey) wx.setStorageSync("history".history); }}, // fetch the cache every time the display changes, givehistoryAnd,forCome out. onShow:function () {    this.setData({      history: wx.getStorageSync("history") | | []})}Copy the code

WXML corresponding section

<! -- search history --> <view class="{{showView? 'hot_keys':'header_view_hide'}}">    <view class="title history"> Search history </view> <icontype='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>Copy the code


5. Home page section

The home page is basically the structure design, as well as the rotation and menu switching, mainly when testing our WXSS skills and JS interaction skills.

1. Style and structure design

Structural design is basically no big difficulty, xiaobian will not talk nonsense, details see Github project (portal). The results are shown below:

                                                                                                

2. Slide menu to switch & Rotation

A. The sliding switch of the menu is actually very simple. Before implementing it, you need to know a few tags: swiper,swiper-item, Scrollview; Slider view container. Swiper: Only swiper-item components can be placed. Otherwise, undefined behavior will occur. Scroll-view Indicates the area of the scrollable view. To use vertical scrolling, you need to give the scrollview a fixed height, using WXSS to set height. The default unit of length for component properties is px, and 2.4.0 supports passing in units (RPX /px). B. Sliding menu switching Bind a bindchange=’swiperChange’ event to swiper, trigger the ‘swiperChange’ event every time the user swipes the page, and define a data variable curentIndex in js. By listening for the touch event in if(e.daile.source == ‘touch’), let curentIndex log every swiper-item swiped. Wx :if=”{{curentIndex == 0}} to determine whether the current swiper-Item is displayed, so as to achieve the effect of sliding menu. Also, the index of the menu bar is judged against the curentIndex so that the specified menu is highlighted. C. Implementation process 1. WXML part:

<! -- Video menu type --> <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>
Copy the code

Part 2. Js

// Change swiper swiperChange:function(e) {/ / switchif(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    })  }Copy the code

D. Potholes you might step on

When you use swiper and Scroll View, you will not be able to scroll up or down when the swiper-item contents are out of view. This is the first time you think of “swiper height adaptive”. Here are a few solutions. Solution a:

    
swiperHeight fixed,
swiper-itemDefault absolute positioning and width height 100%, each
swiper-itemThe content is composed of children of a fixed height, and then calculated dynamically based on the number of children
swiperHeight, initial scheme (since RPX ADAPTS to screen width,
child_heightuse
rpxConvenient adaptive child square case) :


swiper_height = child_height * child_num


The screen effect only fits perfectly on devices with width 375 (IP6, IP x), with extra gaps at the bottom on all other devices, and gradually getting larger as the content increases during pulp-up loading.

Scheme 2:

swiper_height = child_height * child_num * ( window_width / 750 )Copy the code

Swiper = swiper; swiper = swiper; swiper = swiper; swiper = swiper; swiper = swiper This tag style is the same as the width and height of the box and is hidden, and then in the onLoad of the page we call wx.createsElectorQuery () to get the actual tag height baseItemHeight (px unit) :

swiper_height = baseItemHeight * child_numCopy the code

The results show that there is no problem with the original IP6 and IP ⅹ. In addition, ip5 with broadband less than 375 is ok, but there is still a gap on devices larger than 375, such as IP Plus series

Solution 3:

  
swiperThere is a Load TAB at the bottom that says “Load more”, which sits just behind the box and passes
wx.createSelectorQuery()In order to get
bottomHowever, you will find that
bottomIs the label
heightadd
topAnd. Space_height = swiper_height-load_top Space_height = swiper_height-load_top Space_height = swiper_height-load_top
space_heightTo modify the
swiper_heightIt shows that the gap is just cleared, but then it turns out that it was picked up during movement
bottomIt’s not fixed, which means that the value can be inaccurate
space_heightCalculation error, the display effect is not up to the requirements

Style =”height:{{ch}} RPX;” , where ch is the data variable in JS to facilitate the dynamic modification of view height. And write the following code in the onLoad function of the hook function in JS:

 onLoad: function(options) {wx.getSystemInfo({success: res => {// convert to RPXlet ch = (750 / res.screenWidth) * res.windowHeight - 180;        this.setData({          ch        })      },    })
}Copy the code

In the formula minus 180, is the small editor’s own debugging data, the specific reduction can be determined according to the specific situation. In fact, the design of the solution is basically the same as the sliding strategy of BETTER-SCOLL.

6. Video playback

     

1. Main body design

A. Main structure design

<! -- 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, video, the 36 sets, 880 million < / text > < / view > < the 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> <! -- series --> <view class="episode">        <view class="top">            <text class="episode-top-title"< span style = "max-width: 100%; clear: both; min-height: 1em"episode-top-text" bind:tap="showModal""> </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> <! -- Title, time, and Play buttons --> <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> <! Popup box --> < 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"< span style = "box-sizing: border-box! Important; word-wrap: break-word! Important;"top-descrese"> {{entitie.score}}分·VIP·{{entitie.type}}· all {{entitie.universe}} set · 880000 </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>Copy the code

B. js interaction

// 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; Data: {entitie: null, ID :null, entities, Clips :null, currentVid:null, episodes: null, tvphide:false,    vid: null,    title: "TV drama",    defn: "Very clear",    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(); }, /** * lifecycle function -- listen for page load */ onLoad:functionWx.getsysteminfo ({success: res => {// convert to RPXlet 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){      returnitem.id == id; }) this.setData({entitie}) // Change the page inside 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 => {            letoriginTop = 0; This.setdata ({top: originTop + this.currIndex * 224.5})}); }, /** * lifecycle function -- listen for the page to complete the first rendering */ onReady:function() {}, /** * lifecycle function -- listen page display */ onShow:function() {}, /** * lifecycle function -- listen page hide */ onHide:function() {}, /** * lifecycle functions -- listen for page unloads */ onUnload:function() {}, /** * page related event handler -- listen to user pull */ onPullDownRefresh:function() {}, /** ** onReachBottom:function() {}, /** * Users click on the upper right corner to share */ onShareAppMessage:function () {    console.log('share success! '}, // Show dialog box showModal:functionVar 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)}, // Hide dialog hideModal:functionVar 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: trueBind (this), 200)}, // Prevents scrolling by defaultstopScroll() {    return false;  }})Copy the code

C. The ‘pits’ you may encounter

When you are designing an introduction, you will notice that the internal slide event of the popup you designed is triggered along with the slide event of the current page, so why is this? If you think about it, it’s bubbling and capturing (see this blog post for details), which are familiar to anyone who has written a Web project. So it also happens in small programs, so this is where you need to know about sliding through. So how to solve this problem?

Add catchTouchMove =”stopScroll” to the view and set stopScroll to 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"< span style = "box-sizing: border-box! Important; word-wrap: break-word! Important;"top-descrese"> {{entitie.score}}分·VIP·{{entitie.type}}· all {{entitie.universe}} set · 880000 </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>Copy the code

Part 2. Js

// Prevents scrolling by defaultstopScroll() {    return false;  }Copy the code

2. Switch TV episodes

                

A. Switch between episodes of a TV series: Get the VID to play the video, replace the vid with the current VID, and then play the video. B. Implementation procedure: 1. Configure the Tencent Video plug-in in the. Json file. As follows:

{  "usingComponents": {    "txv-video": "plugin://tencentvideo/video"  }}Copy the code

2. Used in WXML as follows:

<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>Copy the code

Where, the meanings of attribute configuration in TXV-Video are as follows:

  • Vid: The VID of Tencent Video, used to get the video resources (must)
  • Playerid: PlayerID must be globally unique and can be set to VID. Otherwise, videos will be played incorrectly (mandatory).
  • Autoplay: Whether toplay automatically. true|false
  • Controls: Whether to display the control bar (play, pause, full screen button display)
  • Title: Video title
  • Defn: Video sharpness, default auto, Optional values: Smooth, SD, HD, ULTRA HD, Blu-ray, 4K, Dolby

For other attributes, see the official documentation of Tencent Video plug-in 3.js interaction

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();  }Copy the code

3. Introduce the implementation

                           

A. The introduction part is mainly WXCSS rendering, there is no logic, need to pay attention to, click the drop-down can make the introduction to hide the dropdown, and there is a dropdown process. B. The main codes are as follows:

1. WXML part:

<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"< span style = "box-sizing: border-box! Important; word-wrap: break-word! Important;"top-descrese"> {{entitie.score}}分·VIP·{{entitie.type}}· all {{entitie.universe}} set · 880000 </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>Copy the code

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; }Copy the code

4. The slice part

In the design of the Mosaic part, the most important is how to solve the problem of loading multiple videos in a page rendering. Many people directly use the for loop to place multiple video labels. This is actually a very clumsy way to do it; When clicking on the corresponding image, an onPicClick event will be triggered. At this time, I will get the VID to play and inform the page that I need to play a video. Please give me a video to play the video.

In addition, you need to note that the location of your video must be the location of the icon you click, so that the page picture will not be inconsistent with the location of the video. Also, in this way, pages can slow down your phone’s CPU consumption, which is a very clever trick. Let’s see how to implement this clever trick.

A. WXML parts

<! <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> <! -- Title, time, and Play buttons --> <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>Copy the code

B. Js Interactive part

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 => {            letoriginTop = 0; This.setdata ({top: originTop + this.currIndex * 224.5})}); }Copy the code

C. Pay special attention to the logic in the getTop() method. The purpose is to correct that when you click on an image, the video will appear in the corresponding position, so that the effect of clicking on the image will play.

7. A short video

The module to achieve logic, basic and almost the home page, directly look at the source codeCopy the code

                   

Basic idea: Use swiper and Scrollview to realize left-right menu linkage. The idea of playing videos is basically the same as that of playing video clips.

1. Why do we need to configure JSON? Because we use Tencent video plug-in here and the component of the video tail defined by ourselves, which is used for video sharing operation and comment operation. The configuration is as follows:

{  "usingComponents": {    "txv-video": "plugin://tencentvideo/video"."footer": "/components/footer/footer"  }}Copy the code

2. WXML part

<! -- 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> <! -- Title, time, and Play buttons --> <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>Copy the code

Part 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'*/ data: {curentIndex: 0, shortCategory: shortCategory, Videos: videoUrl, ch: 0, top: 0, currVideo:{}}, // change swiper swiperChange:function(e) {/ / switchif(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)            letoriginTop = res[0].scrollTop; This.setdata ({top: originTop + this.currIndex * 224.5})}); }, /** * lifecycle function -- listen for page load */ onLoad:function(options) {wx.getSystemInfo({success: res => {// convert to RPXlet ch = (750 / res.screenWidth) * res.windowHeight - 80;        this.setData({          ch        })      },    })    this.videoContext = wx.createVideoContext('tvp'); }, /** * lifecycle function -- listen for the page to complete the first rendering */ onReady:function() {}, /** * lifecycle function -- listen page display */ onShow:function() {}, /** * lifecycle function -- listen page hide */ onHide:function() {}, /** * lifecycle functions -- listen for page unloads */ onUnload:function() {}, /** * page related event handler -- listen to user pull */ onPullDownRefresh:function() {}, /** ** onReachBottom:function() {}, /** * Users click on the upper right corner to share */ onShareAppMessage:function() {}, // prevents scrolling by defaultstopScroll() {    return false;  }})Copy the code

8. To me

As for the basic content of my partial implementation, it is to display the user’s profile picture and name, show whether the user has opened the membership, watch history, my order reading and setting functions. Due to time constraints, the small editor only realizes some functions of setting

                 

       1. WXML part

<! --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"</text> <text class="bottom" bindtap="lookBans"</text> </view> <view class="history" bindtap="lookBans">      <image class="icon" src=".. /.. /images/history.png" mode="widthFix"></image> <text> View history </text><text class="fr"></text>    </view>    <view class="play-list history" bindtap="lookBans">      <image class="icon" src=".. /.. /images/view.png" mode="widthFix"></image> <text> </image> <text"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> Set </text><text class="fr"></text>    </view></view>Copy the code

Part 2. Js

// miniprogram/pages/mine/mine.jsconst utils = require('.. /.. /utils/utils.js'NavigatItem (e) {navigatItem(e) {navigatItem(e) {navigatItem(e) {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: 'Not developed yet! ',      showCancel: false,      confirmColor: '#FF4500', success (res)}, {}}) / * * * life cycle function - listening page load * / onLoad:function (options) {    if (app.globalData.userInfo) {      this.setData({        userInfo: app.globalData.userInfo      })    }else{// Handle wx.getUserInfo({success: res => {app.globaldata.userInfo = res.userinfo; Console. log(res.userinfo) this.setData({userInfo: res.userinfo})}}, /** * lifecycle function -- listen to the page when the first rendering is complete */ onReady:function() {}, /** * lifecycle function -- listen page display */ onShow:function() {}, /** * lifecycle function -- listen page hide */ onHide:function() {}, /** * lifecycle functions -- listen for page unloads */ onUnload:function() {}, /** * page related event handler -- listen to user pull */ onPullDownRefresh:function() {}, /** ** onReachBottom:function() {}, /** * Users click on the upper right corner to share */ onShareAppMessage:function() {}})Copy the code

3. What you need to pay attention to

When implementing the Settings function section, this small editor writes a common utility function in utils for page jumps and so on. Utils.js source is as follows:

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}Copy the code

Project complete source code:

Github.com/hongdeyuan/…

9. Conclusion

Xiaobian in writing this project, stepped on a lot of pits, here only write a few. Although it is more convenient to use a frame in some places, I think I can improve my ability to write projects freehand. Finally, thank you for coming to study this article, thank you for your support, welcome to study and discuss. If you like this article or can help you, give it a thumbs up!