There are countless short videos gone, but the main body is still carried by app. This article describes how H5 realizes the video sliding experience of APP. Silence wins, a picture is worth a hundred arguments, and take a look at the picture below:

Url link (to be viewed in wechat or Mobile Q)

As can be seen from the figure above, our main functions are also explained in this paper:

  • Scroll up to see the new short video
  • Slide down to view short history videos
  • Roll up if the slide distance is greater than one distance, otherwise return to position
  • Sliding has the concept of speed, if the speed reaches a certain value, the distance is not enough, will also turn the page
  • Auto-play problem
  • How to ensure the smoothness of the current video

Let’s do it ~~~~~~~~~~~~~~~~

The text of this article can be carefully read, a lot of mining road summary. Small program slide video of the road to the pit follow.

HTML + CSS page structure

The page layout uses UL layout as follows:

Main styles:

.quan_vcd_list-item{
    width:100%;
    height:100vh;
    overflow:hidden;
    position:relative;
    -webkit-overflow-scrolling:touch;
}
.quan_vcd_list.trans{
    transition:transform .3s ease-out 0s,-webkit-transform .3s ease-out 0s;
}
video .video_hide{
    left: -100%;
}
Copy the code

Each item fills the screen, adding animation when it is released.

<video
  x5-video-can-play-with-audio="true"
  :class="feedData.hide? 'video_hide':''"
  :src="feedData.playurl"
  preload="auto"
  type="video/mp4"
  width="100%"
  webkit-playsinline="true"
  playsinline="true"
  loop="loop"
></video>
Copy the code

Video HTML, two of three important points:

  • The video needs to be moved out of the screen to hide the video. If display: None is used, the user may click on it abnormally.
  • Set playsinLine so that the video plays on the current page instead of full screen.
  • X5-video-can-play-with-audio =true, and play the audio before the video. If this value is not set, the audio will stop automatically after the video is played on the wechat H5 page.

Ul is an outer list that is 100% wide. Each video item is a node that fills the screen. This article does not focus on, interested in their own try.

Core JS code

Listen for user gestures, dynamically set the translateY of UL, and then perform audio and video playback. so easy!

Gestures sliding

Gesture slide play video turn page, there are two main conditions: sliding distance and sliding acceleration.

  1. Initializing Touch mainly resets parameters, records the last displacement, and stores the height of the page. Bind the Touch event.
function Touch() {
  this.init();
}
Touch.fn = Touch.prototype;
Touch.fn.init = function(params) {
  this.lastY = this.translateY; // Record the current displacement
  this.startY = 0;
  this.moveY = 0;
  this.lock = 0; // If sliding is not allowed during the callback or ascent phase.
  this.bodyHeight = window.innerHeight;
  this.resetLimitY();
  self.isIOS = /ip(? :hone|ad|od)/.test(navigator.userAgent.toLowerCase());
  ["touchstart"."touchmove"."touchend"].forEach(str= > {
    document.addEventListener(this.this[str].bind(this));
  });
};
Copy the code
  1. Listen for gestures sliding

Here added a lock variable, used when the page is in the page-turning state, need to lock the slide, otherwise it will mess up. LastY is the final state distance after the last slide. TranslateY refers to the state distance during the slide. It is necessary to change the value of translateY of UL in real time

Detect slip acceleration: detectAcc

var a = Math.abs(this.moveY) / (Date.now() - this.start) >= 0.5;
Copy the code

If the sliding distance is greater than half of the sliding time difference, it is considered as active page turning and the page turning operation needs to be performed.

One thing to note here: when you swipe on ios, you can turn the page immediately, which is a great experience, but android doesn’t, so Android needs to wait for TouchEnd before turning the page

Touch.fn.touchstart = function(e) {
  if (this.lock) return;
  this.startY = e.touches[0].pageY;
  this.start = Date.now(); // Identifies the slide start time, also used to identify the slide start
};
Touch.fn.move = function(y) {
  this.translateY = y;
};
Touch.fn.touchmove = function(e) {
  if (this.lock || !this.start) return; // Locked, or no start, mainly because the gesture has been sliding acceleration away, the gesture needs to be released and start again
  this.moveY = e.touches[0].pageY - this.startY;
  this.move(this.moveY + this.lastY);
  this.detectAcc();
};
/** * Detect acceleration */
Touch.fn.detectAcc = function(isEnd) {
  // console.log(self.moveY+" "+(Date.now()-self.start));
  var a = Math.abs(this.moveY) / (Date.now() - this.start) >= 0.5;
  if (isEnd) {
    if (a) {
      this.limitY = 0;
    }
    this.movend();
    return;
  }
  if (this.isIOS && a) {
    //IOS, you can swipe directly when touchMove, smooth experience.
    this.limitY = 0;
    this.movend(); }};Copy the code
  1. Handle page turning operations

Set the minimum distance you need to slide to turn a page. This should be more than 1/3 screen height. If the user slides more than the minimum distance, the page is turned; otherwise, the page is returned. We don’t accept gestures, so we have to wait until the shift is over before resetting the Lock flag. You also use dynamic pull for the next video (I’m pulling two videos at a time here), so you need to issue an eventsBus event to accept that event to pull the new video.

Touch.fn.resetLimitY = function () {
    this.limitY = this.bodyHeight/3;// How much displacement does it take to slide
}
Touch.fn.touchend = function (e) {
    if(this.lock||this.moveY==0| |!this.start) return;
    this.detectAcc(1);
}
Touch.fn.movend = function () {
    const self = this;
    this.lock = 1;
    /*** * If the upper and lower displacement is less than the minimum, it will revert to the previous displacement, * otherwise, it will translate up or down by one body width. * /
    let transformY = Math.abs(self.moveY)<self.limitY? self.lastY:self.lastY+self.bodyHeight*(self.moveY>0?1:- 1);

    /*** * also need to calculate the maximum sliding height and maximum upsliding height */
    let listUL = document.querySelector(".quan_vcd_list");
    let listHeight = listUL.getBoundingClientRect().height;

    // If it is the last li, it cannot slide,
    let maxBottom = (listHeight - self.bodyHeight)*- 1;
    let lastComputeY = transformY>0?0:transformY<maxBottom? maxBottom:transformY;// After you stop sliding, you automatically scroll distance, transition
    listUL.classList.add('trans');
    if(lastComputeY<=0) {let d = lastComputeY-self.lastY;
        d&&events.trigger("touch_move",d,(-lastComputeY/self.bodyHeight));
    }
    self.start = 0;
    (window.requestAnimationFrame|| window.webkitRequestAnimationFrame)(function () {
        self.move(lastComputeY);
        self.moveY = 0;
        self.lastY = lastComputeY;// Record the location
        if(listHeight + self.lastY<=self.bodyHeight){
            events.trigger("turnPage");
        }
        setTimeout(function () {
            listUL.classList.remove("trans");
            self.lock = 0;
            self.resetLimitY();
        },500);
    });
Copy the code

Registering video Components

Both video and background sound controls. If the video has carried out its own cost and waiting several times in a sequence, that means the video is stuck and the loading box needs to be shown. Onx5videoexitfullscreen onx5VideoExitFullScreen onx5VideoExitFullScreen onx5VideoExitFullScreen onx5VideoExitFullScreen

export default function videoComponent(opt) {
  var config = {
    props: ["feedData"."index"].data: function() {
      return {
        play_btn: 0.bg_name: "".anim_like: [].vloading: 0
      };
    },
    mounted:function(){
        this.addEvent();
        this.stall = 0;
        this.loaderror = 0;
    },
    methods: {
      onstalled: function() {
        if (!this.feedData.start) return;
        this.vloading = 1;
        this.play();
        this.stall++;
        if (this.stall == 2) {
          showTip("The Internet is a little slow.");
          store.report(27.1); }},waiting: function() {
        clearInterval(this.timer);
        this.loadTimes = 0;
        this.timer = setInterval((a)= > {
          this.loadTimes++;
          if (this.loadTimes >= 2) {
            // If it is not played for three consecutive times, it is considered to be stuck
            this.aPause();
            this.vloading = 1; }},1800);
      },
      ondurationchange: function() {
        this.compute();
      },
      onloadedmetadata: function() {
        this.compute();
      },
      ontimeupdate: function() {
        this.timeupdate();
      },
      aPause: function() {
        this.audio && this.audio.pause();
      },
      aPlay: function() {
        this.audio && this.audio.play();
      },
      pause: function() {
        this.video.pause();
        this.aPause();
        this.vloading = 0;
        clearInterval(this.timer);
      },
      play: function(isMove) {
        this.videoPlay(isMove);
      },
      checkLoading: function() {
        checkLoading(this);
      },
      onx5videoexitfullscreen: function() {
        this.video.play(); }}}; Vue.component("video-com", util.assign(config, opt));
}
Copy the code

Register event

Register the event of video, handle background music, and monitor the page turning event of gesture sliding above.

In the wechat environment, wait until the WX API is loaded. In Android wechat, the video cannot be automatically played, otherwise the video can be automatically played.

function addEvent() {
    this.$nextTick( (a)= > {
        this.video = this.$el.querySelector("video");
        var arry = ['stalled'.'playing'.'timeupdate'.'abort'.'error'.'durationchange'.'loadedmetadata'.'pause'.'x5videoexitfullscreen'];
        arry.forEach( (str) = > {
            this.video.addEventListener(str,this['on'+str]);
        });
        if(this.index==0) {this.loadWX(function (isWx) {
                if(isWx&&isAndroid) return;
                this.play(); }); }});this.handleBGM(this.feedData);
    let self = this;
    events.listen("touch_move", (direct,i)=> {
        handleMove(self,direct,i);
    });
}
function loadWX(cb) {
    if(device.scene=="weixin") {if(window.WeixinJSBridge){
            cb(true);
        }else{
            document.addEventListener("WeixinJSBridgeReady".function() {
                cb(true); }); }}else{ cb(); }}Copy the code

preload

The premise of preloading is that the current video has been loaded, and then the next video can be preloaded. You need to mark whether each video has loaded.

  • If the current video is not loaded, the system will remove all preloaded videos to prevent occupying the current network
  • If the current video is already loaded, keep preloading the next one and the next one, especially if the network is good.
function checkLoading() {
    let self = this;
    var interval = window.setInterval(getLoaded,100);
    // Get how long the video has been downloaded
    function getLoaded() {
        var end = 0;
        try {
            end = parseInt(self.video.buffered.end(0) | |0) +2;
        } catch(e) {
        }
        if(end>=self.duration){
            clearInterval(interval);
            self.loadedAll = 1;
            var nextItem = store.store.feedList[self.index+1];
            if(nextItem){// The next entry exists
                // The video has not been loaded yet.
                if(! nextItem.playurl) nextItem.playurl = nextItem.videourl;// There is background music, but the background music is not finished loading, then start loading
                if(! nextItem.bgmurl_p&&nextItem.bgmurl){ nextItem.bgmurl_p = nextItem.bgmurl; }}}return end
    }
}
Copy the code

Slide video processing

Sliding play is the key, it is necessary to ensure that the user gesture and play need to be synchronized, otherwise part of the mobile phone play failure, need to click more play.

  • If the current video is played, the current video and audio must be played
  • You need to stop videos and audio files that have not been downloaded (by emptying the URL of the video), otherwise the current playback will be affected. For those that have already downloaded, no processing is required.
  • A maximum of 16 videos can be displayed on a page (limited by ios). The current video is centered on the page and seven videos are reserved up or down. Display: None
function handleMove(self, direct, i) {
  if (i == self.index) {
    handleCurrent(self);
  }
  //direct>0 is used to pause the next video in the current position.
  // Direct <0 slides up, the page is about to play the next video, and the previous video in the current position is paused
  if (self.index == i + (direct > 0 ? 1 : - 1)) {
    self.pause();
    if(! self.loadedAll) { feed.playurl =""; // If the load is not complete, then do not load.
      feed.start = 0;
      feed.hide = 1;
    }
    if(! self.audioLoaded) { feed.bgmurl_p =""; }}if (self.index >= i + 7 || self.index <= i - 7) {
    feed.maxHide = 1; // If the maximum number of nodes exceeds 16, the node is hidden.
    feed.playurl = ""; // If the maximum number of nodes exceeds 16, other videos will be eliminated.
    feed.start = 0;
    feed.hide = 1;
  } else {
    feed.maxHide = 0; }}function handleCurrent(self){
    if(! self.feed.playurl) { self.feed.playurl = self.feed.videourl;if(! self.feed.bgmurl_p && self.feed.bgmurl) { self.feed.bgmurl_p = self.feed.bgmurl; self.audio.load(); } self.video.load(); } self.$nextTick((a)= > {
      store.addPlayNum(feed.shareid);
      if(self.audio && ! self.audioLoaded) {var int = setInterval((a)= > {
          if (self.audioLoaded) {
            clearInterval(int);
            self.play(1); }},100);
      } else {
        self.play(1); }}); }Copy the code

On the Error

The playback on different mobile phones may enter error (abort,error,stall). We need to start playing again automatically. This can solve the problem of secondary playback on some mobile phones.

function errors(msg) {
    let self = this;
    if(! self.video||! self.feedData.start)return;
    self.loaderror++;
    if(self.loaderror<=2){
        self.play();
        return;
    }
    setPlay(1);
    // report msg 
}
Copy the code

Github.com/antiter/blo…