亲宝软件园·资讯

展开

Vue3版抖音滑动插件

好学习吧丶 人气:4

起步

年前单位需要搞一个类似抖音的需求,这本应是客户端的任务,然而,不知天高地厚的我却接了下来,然而下细致调研之下,发现网上并没有成熟的方案,但是却又很多需求,各大论坛全是提问的帖子,却少有人回答和解决。

这一瞬间,俺慌了,毕竟单位的活,排期都是定死的,这时候临阵退缩,实乃下下策。于是只能撸起袖子加油干。毕竟自己揽的事,含着泪也要干完,这就是男人,一个吐沫一个钉!

调研

大家知道,web端比起客户端的劣势有几点,想要做出类似客户端的复杂的交互效果,需要考虑几个问题:

而在我调研了抖音的web端、git上的一些开源的相关项目、以及一些零零散散的回答之后,发现都不太匹配 他们在实现上,那么只能集几百家之长自己来了,既然自己来就需要针对当前三个问题来寻找既能解决问题,又能快速实现的方案(毕竟有排期)

实现思路

在实现的初步设想中,我们不只需要解决问题,其实也需要考虑一些架构设计,也就是你怎样去将关注度分离,怎样将组件的颗粒度拆的细致,能将每一个组件独立出来,外部单独引用,怎样将每一个组件做通用,方便日后维护,并且还能快速开发,不耽误排期,这其实就是你在这做也无需求之初需要去想的一些问题,总结如下

组件设计的设想俺才疏学浅也就能想到这了,接下来就该解决在调研中发现的三个问题:

工程构建

工程构建为了装逼上了最新的vite ,体验了一把,开发体验确实是丝滑快速。由于vite天生支持库的开发,只需要在vite.config.ts 添加build内容即可

build: {
    lib: {
      entry: path.resolve(__dirname, 'src/components/index.ts'),
      name: 'videoSlide',
      fileName: (format) => `index.${format}.js`
    },
    rollupOptions: {
      // 确保外部化处理那些你不想打包进库的依赖
      external: ['vue'],
      output: {
        // 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
        globals: {
          vue: 'Vue'
        }
      }
    }
  },

由于库可能给ts大佬使用,需要安装vite-plugin-dts 插件,来生成d.ts文件

代码实现

由于视频内容和轮播部分的处理是两个独立的逻辑,所以将代码拆分为两个组件video.vue以及slide.vue

video实现

video的实现的基本思路就是重写原生video 标签默认ui来达到自定义的目的,样式就不在赘述,主要就是video提供的一些事件重写video默认行为,这里简述下重点的函数

// 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 }) {
    // 是否是暂停状态
    const paused = ref(true);
    // 视频总时间
    const endTime = ref(second(0));
    //播放的时间
    const startTime = ref(second(0));
    // 是否是按下状态
    const isPress = ref(false);
    //缓冲进度
    const percentageBuffer = ref(0);
    // 播放进度
    const percentage = ref(0);
    // 保存计算后的播放时间
    const calculationTime = ref(0);
    // 拿到video 实例
    const video = ref(null);
    // 是否展示封面图
    const showImg = ref(true);
    // 是否处于缓冲中
    const loading = ref(false);
    // 播放
    function play() {
      video.value.play();
      paused.value = false;
    }
    // 暂停
    function pause() {
      if (paused.value) return;
      video.value.pause();
      paused.value = true;
      loading.value = false;
    }
    // 获取缓冲进度
    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
      );
    }
    // 时间改变
    function durationchange() {
      endTime.value = second(video.value.duration);
      console.log("时间改变触发");
    }
    // 首帧加载触发,为了获取视频时长
    function loadeddata() {
      console.log("首帧渲染触发");
      showImg.value = false;
      autoplay && play();
    }
    //当播放准备开始时(之前被暂停或者由于数据缺乏被暂缓)被触发
    function playing() {
      console.log("缓冲结束");
      loading.value = false;
    }
    //缓冲的时候触发
    function waiting() {
      console.log("处于缓冲中");
      loading.value = true;
    }
    // 时间改变触发
    function timeupdate() {
      // 如果是按下状态不能走进度,表示需要执行拖动
      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
      );
    }
    // 按下开始触发
    function touchstart() {
      isPress.value = true;
    }
    //松开按钮触发
    function touchend() {
      isPress.value = false;
      video.value.currentTime = calculationTime.value;
    }
    // 拖动的时候触发
    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);
    }
    //点击进度条触发
    function handleProgress(e) {
      touchmove(e);
      touchend();
    }
    // 播放结束时触发
    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,
    };
  },

需要注意的是,需要自定义内容交给了使用者去自定义,全部通过插槽传入当前组件,这样就方便了根据内容自定义样式了

slide.vue

slide.vue 就是处理滑动内容的组件,他包含了常用的上拉刷新,预加载等内容核心代码如下:

// 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) {
      //表示没有滑动,不做处理
      if (activeIndex.value === swiper.activeIndex) {
        // 表示是第一个轮播图
        if (swiper.swipeDirection === "prev" && swiper.activeIndex === 0) {
        // 表示上拉刷新
          emit("refresh");
        } else if (
          swiper.swipeDirection === "next" &&
          swiper.activeIndex === list.length - 1
        ) {
          // 滑动到底部
         emit("toBottom");
        }
      } else {
        activeIndex.value = swiper.activeIndex;
        // 为了预加载视频,提前load 数据
        if (swiper.activeIndex === list.length - 1) {
          emit("load");
        }
      }
    }
    return {
      transitionStart,
      activeIndex,
    };
  },

需要注意的是有两点

组合使用

组合使用其实就非常简单了:

//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">点赞</div>
            <div class="right-btn comment" @click="comment">评论</div>
            <div class="right-btn collection" @click="collection">收藏</div>
            <div class="right-btn share" @click="share">分享</div>
          </div>
        </div>
      </Yvideo>
    </Yslide>

在组合使用中,我将video通过插槽的方式传入silide内部,这样做的原因是,为了用户能自定义传入内容,这也是很多插件库惯用的伎俩,增加了组件的灵活性,又增加了组件的独立性

视频自动播放问题

在web浏览器中你经常会看到DOMException: play() failed because the user didn't interact with the document first 这个问题,

首先可以肯定的是在web浏览器中在与浏览器没有交互的情况下是不允许自动播放的,目前暂时还无法突破这个限制

如果你要嵌入app中,webview 可以突破,具体方法大家可自行查询,网上教程数不胜数。

git地址

将插件地址奉上,供大佬们参考,如有需求可直接引用,也可,克隆下来自行修改,如有问题请提issues github.com/yixinagqing

总结

加载全部内容

相关教程
猜你喜欢
用户评论