Achieve a Vue3 version of the tiktok slide plug pits guide!

"This is the 26th day of my participation in 2022's first update challenge. For details: 2022 first revision challenge」.

start

Years ago, the unit needed to make a demand similar to the jitter. This should be the task of the client. However, I did not know tiktok. But I did not know the detailed plan of the Internet. However, there were many demands. The major forums were all questions, but few answered and solved them.

At this moment, I panicked. After all, the work and schedule of the unit are doomed. At this time, it's the worst policy to retreat. So I had to roll up my sleeves and work hard. After all, I have to finish what I do with tears. This is a man, a spit and a nail!

investigation and research

As we all know, the web side has several disadvantages over the client side. To make complex interaction effects similar to the client side, we need to consider several issues:

  • 1. Performance - how to achieve optimal performance (of course, it is impossible to compare with the client)
  • 2. Experience - how to achieve the excellent experience of the client, such as how to handle the video buffer, how to handle the initialization, and whether to pre load the video
  • 3. Compatible -- ios and Android devices and the implementation of browsers between their versions are slightly different

And after I researched some web related tiktok, some open source related projects on git, and some 00 scattered answers, I found that they were not very match. They could only collect hundreds of them. Since they came to need three problems, they could find solutions that could solve problems and quickly implement them.

Realization idea

In the preliminary idea of implementation, we not only need to solve the problem, but also need to consider some architecture design, that is, how to separate the attention, how to disassemble the granularity of components in detail, how to separate each component and reference it externally, and how to make each component universal to facilitate future maintenance, and how to develop quickly without delaying the schedule, In fact, these are some problems you need to think about when you don't need to do it here. It is summarized as follows

  • First of all, there is no doubt that in order to achieve the sliding effect, the fastest ready-made scheme is swiper, and swiper's position on the web side is unshakable.
  • Secondly, we all know that the native video tag experience is a mess, so this one needs to be implemented by ourselves, such as progress bar, dragging, pause playback, buffering and so on. And some elements like video, such as tiktok, sharing, need to be imported externally, so that other developers can customize them when they are using them.
  • How to split the structure of components and upload npm separately for everyone to use

I can think of the idea of component design because of my lack of talent and learning. Next, I should solve the three problems found in the research:

  • The easiest problem to solve is the compatibility problem. babel solves it perfectly. cli tool command line is generated directly. swiper tries to use the old version when it can realize the function
  • Performance problems are the most diff icult to solve. If we render many videos, there will be many video tiktok in dom. Here we use web shake, and render the empty nodes in one video of the video of the active sidle in the whole dom, so that the number of dom can be greatly reduced.
  • Although the experience problem is not difficult, it cannot be solved by relying on the front end alone. It requires multi-party cooperation. It needs to compress the video size, provide cover images, increase buffer effects, etc. Moreover, the performance of different devices, different systems and different versions in video is very different. In fact, this is a compatibility problem that can not be solved by technology. So, We can only solve the problem interactively.

Engineering construction

In order to install the latest vite, the project construction has experienced it, and the development experience is really smooth and fast. Because vite naturally supports the development of library, it only needs to be in vite config. Add build content to ts

build: {
    lib: {
      entry: path.resolve(__dirname, 'src/components/index.ts'),
      name: 'videoSlide',
      fileName: (format) => `index.${format}.js`
    },
    rollupOptions: {
      // Be sure to externalize dependencies that you don't want packaged into the library
      external: ['vue'],
      output: {
        // In UMD build mode, a global variable is provided for these externalized dependencies
        globals: {
          vue: 'Vue'
        }
      }
    }
  },

Since the library may be used by the TS boss, the vite plugin DTS plug-in needs to be installed to generate the d.ts file

code implementation

Since the processing of video content and rotation is two independent logic, the code is divided into two components, video Vue and slide vue

video implementation

The basic idea of video implementation is to rewrite the default ui of the native video tag to achieve the purpose of customization. The style will not be repeated. It is mainly that some events provided by video rewrite the default behavior of video. Here is a brief description of the key functions

// vue
 <video
     
      playsinline="true"
      webkit-playsinline="true"
      mediatype="video"
      :poster="poster"
      @progress="progress"
      @durationchange="durationchange"
      @loadeddata="loadeddata"
      @playing="playing"
      @waiting="waiting"
      @timeupdate="timeupdate"
      @canplay="playing"
      @ended="ended"
    >
      <source :src="src" type="video/mp4" />
    </video>
    //js
    
   setup({ autoplay }) {
    // Is it suspended
    const paused = ref(true);
    // Total video time
    const endTime = ref(second(0));
    //Playback time
    const startTime = ref(second(0));
    // Is it pressed
    const isPress = ref(false);
    //Buffer progress
    const percentageBuffer = ref(0);
    // Playback progress
    const percentage = ref(0);
    // Save calculated playback time
    const calculationTime = ref(0);
    // Get the video instance
    const video = ref(null);
    // Is the cover image displayed
    const showImg = ref(true);
    // Is it in buffer
    const loading = ref(false);
    // play
    function play() {
      video.value.play();
      paused.value = false;
    }
    // suspend
    function pause() {
      if (paused.value) return;
      video.value.pause();
      paused.value = true;
      loading.value = false;
    }
    // Get buffer progress
    function progress() {
      if (!video.value) return;
      percentageBuffer.value = Math.floor(
        (video.value.buffered.length
          ? video.value.buffered.end(video.value.buffered.length - 1) /
            video.value.duration
          : 0) * 100
      );
    }
    // Time change
    function durationchange() {
      endTime.value = second(video.value.duration);
      console.log("Time change trigger");
    }
    // The first frame loading is triggered to obtain the video duration
    function loadeddata() {
      console.log("First frame rendering trigger");
      showImg.value = false;
      autoplay && play();
    }
    //Triggered when playback preparation starts (previously suspended or suspended due to lack of data)
    function playing() {
      console.log("End of buffer");
      loading.value = false;
    }
    //Triggered when buffering
    function waiting() {
      console.log("In buffer");
      loading.value = true;
    }
    // Time change trigger
    function timeupdate() {
      // If you can't move the progress in the pressed state, it indicates that you need to drag
      if (isPress.value || !video.value) return;
      startTime.value = second(Math.floor(video.value.currentTime));
      percentage.value = Math.floor(
        (video.value.currentTime / video.value.duration) * 100
      );
    }
    // Press start trigger
    function touchstart() {
      isPress.value = true;
    }
    //Release button trigger
    function touchend() {
      isPress.value = false;
      video.value.currentTime = calculationTime.value;
    }
    // Triggered when dragging
    function touchmove(e) {
      const width = window.screen.width;
      const tx = e.clientX || e.changedTouches[0].clientX;
      if (tx < 0 || tx > width) {
        return;
      }
      calculationTime.value = video.value.duration * (tx / width);
      startTime.value = second(Math.floor(calculationTime.value));
      percentage.value = Math.floor((tx / width) * 100);
    }
    //Click progress bar to trigger
    function handleProgress(e) {
      touchmove(e);
      touchend();
    }
    // Trigger at the end of playback
    function ended() {
      play();
    }
    onMounted(() => {});
    return {
      video,
      paused,
      pause,
      play,
      progress,
      durationchange,
      loadeddata,
      endTime,
      startTime,
      playing,
      percentage,
      waiting,
      timeupdate,
      percentageBuffer,
      touchstart,
      touchend,
      touchmove,
      isPress,
      ended,
      handleProgress,
      loading,
      showImg,
    };
  },

It should be noted that the content to be customized is left to the user for customization, and all the content is transferred into the current component through the slot, which makes it convenient to customize the style according to the content

slide.vue

slide.vue is a component that handles sliding content. It contains common contents such as pull-up, refresh and preload. The core code is as follows:

// vue
  <swiper
    direction="vertical"
    @transitionStart="transitionStart"
  >
    <swiper-slide class="slide-box" v-for="(item, index) in list" :key="index">
      <slot
        :item="item"
        :index="index"
        :activeIndex="activeIndex"
        v-if="activeIndex >= index - 1 && activeIndex <= index + 1"
      ></slot>
    </swiper-slide>
  </swiper>
  //js
   setup({ list }, { emit }) {
    const activeIndex = ref(0);
    function transitionStart(swiper) {
      //Indicates that there is no sliding and no processing is required
      if (activeIndex.value === swiper.activeIndex) {
        // Indicates the first rotation chart
        if (swiper.swipeDirection === "prev" && swiper.activeIndex === 0) {
        // Indicates pull-up refresh
          emit("refresh");
        } else if (
          swiper.swipeDirection === "next" &&
          swiper.activeIndex === list.length - 1
        ) {
          // Slide to bottom
         emit("toBottom");
        }
      } else {
        activeIndex.value = swiper.activeIndex;
        // In order to preload the video, load the data in advance
        if (swiper.activeIndex === list.length - 1) {
          emit("load");
        }
      }
    }
    return {
      transitionStart,
      activeIndex,
    };
  },

It should be noted that there are two points

  • In order to preload the data, you will request the data when sliding to the last frame, but because the request is asynchronous, if you slide to the bottom quickly when sliding to the last video, the event of sliding to the bottom will be triggered. In fact, the new data request will not be the bottom after it comes back. At this time, you need to make a judgment, If you are sliding to the bottom in the request, don't deal with your logic
  • For the sake of performance, only active, prev and next contents are rendered, and all others render empty nodes. In order to prevent multiple vidoe tags from appearing in the page, prev and next only render the contents of the default graph

Combined use

The combination is actually very simple:

//vue
 <Yslide
      :list="data"
      v-slot="{ item, index, activeIndex }"
      @refresh="refresh"
      @toBottom="toBottom"
      @load="load"
    >
      <Yvideo
        :src="item.entStoreVO.video"
        :poster="item.entStoreVO.videoImg"
        :index="index"
        :activeIndex="activeIndex"
        autoplay
      >
        <div class="mantle">
          <div class="right" @click.stop="">
            <div class="right-btn fabulous" @click="fabulous">give the thumbs-up</div>
            <div class="right-btn comment" @click="comment">comment</div>
            <div class="right-btn collection" @click="collection">Collection</div>
            <div class="right-btn share" @click="share">share</div>
          </div>
        </div>
      </Yvideo>
    </Yslide>

In the combined use, I transfer the video into the inside of silide through the slot. The reason for this is that in order to enable users to customize the incoming content, this is also a trick commonly used by many plug-in libraries, which increases the flexibility and independence of components

Video auto play problem

In web browsers, you often see DOMException: play() failed because the user didn't interact with the document first,

First of all, it is certain that in the web browser, automatic playback is not allowed without interaction with the browser. At present, this limit cannot be broken through

If you want to embed in the app, webview can break through. You can query the specific methods by yourself. There are countless online tutorials.

git address

Send the address of the plug-in for the reference of the leaders. If you need it, you can directly quote it or clone it and modify it yourself. If you have any questions, please mention issues https://github.com/yixinagqingyuan/video-slide

Added by shooff2332 on Wed, 09 Mar 2022 15:41:30 +0200