文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

关于实现Vue3版抖音滑动插件踩坑指南

2024-04-02 19:55

关注

起步

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

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

调研

大家知道,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…

总结

到此这篇关于实现Vue3版抖音滑动插件踩坑指南的文章就介绍到这了,更多相关Vue3版抖音滑动插件内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-前端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯