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

目录
  • 起步
  • 调研
  • 实现思路
  • 工程构建
  • 代码实现
    • video实现
    • slide.vue
    • 组合使用
  • 视频自动播放问题
  • git地址
  • 总结

起步

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

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

调研

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

  • 1、性能--怎样能达到最优(当然想要跟客户端比肩这是不可能的)
  • 2、体验--怎样达到客户端的优秀体验,比如视频缓冲怎么处理,初始化怎么处理,要不要视频预加载
  • 3、兼容--ios 和安卓的设备以及他们各个版本之间的浏览器的实现都略有不同

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

实现思路

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

  • 首先来说毋庸置疑的是:要想实现滑动的效果,现成的方案最快的就是swiper,swiper在web端的地位也是不可动摇。
  • 其次原生video标签体验大家也都知道,一塌糊涂,那么这一块也就需要自己实现,比如进度条,拖动,暂停播放,缓冲中等等内容。并且类似抖音中的视频上方的一些元素,比如点赞,分享等功能需要外部传入,让别的开发者在使用时自己定制
  • 怎样将组件的的结构拆分出来,能单独打包上传npm 供大家使用

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

  • 最容易解决的问题就是兼容问题,babel完美解决,cli工具命令行直接生成,swiper 在能实现功能的情况下尽量使用老的版本
  • 性能问题是最难解决,如果渲染到很多视频之后,难免会有很多的video存在于dom中,这里我们采用了web抖音的方案,在整个dom中只渲染一个活动sidle的video其他的slide中渲染空节点,这样就能大大减少dom的数量,再配合vue的diff 能提供一个还算过得去的性能
  • 体验问题虽然不难,但是仅仅靠前端是无法解决的,需要多方配合,他需要压缩视频大小,提供封面图,增加缓冲效果,等等,而且不同的设备不同系统不同版本在video的表现差异还非常大,这其实是一个不可用技术解决的兼容问题,那么,我们只能从交互上来解决问题。

工程构建

工程构建为了装逼上了最新的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,
    };
  },

需要注意的是有两点

  • 为了预加载数据会在滑动到最后一帧的时候去请求数据,但是由于请求是异步的,如果在滑动到最后一个视频的时候在快速下滑会触发滑动到底部的事件,这时候其实新数据请求回来之后便又不是底部了,这时候则需要你去做个判断,如果正在请求中滑动到底部不去处理你的逻辑
  • 为了性能考虑,只渲染了active 、prev、next内容,其他一律渲染空节点,并且为了防止页面中出现多个vidoe标签,prev 和next 只渲染默认图内容

组合使用

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

//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版抖音滑动插件内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 基于vue的fullpage.js单页滚动插件

    基于vue的fullpage.js使用方法,供大家参考,具体内容如下 功能概述 可实现移动端的单页滚动效果,支持横向滚动和纵向滚动 兼容性 目前还未进行大规模兼容性测试.有bug请提问至issues 安装 npm install vue-fullpage --save commonjs import VueFullpage from 'vue-fullpage' Vue.use(VueFullpage) 或 var vueFullpage = require('vue-fullpage') Vu

  • vue滚动轴插件better-scroll使用详解

    跟做慕课网的vue高仿外卖项目中用到了一个很好用的插件BScroll,用来计算左侧menu栏对应右侧foods栏相应显示的食物区,如果不用插件就比较费事了,因此这里分享一下这个插件的简单使用: 一.项目中下载,并引入 在配置文件package.json中引入版本 "dependencies": { "better-scroll": "^0.1.7" } 然后进入项目目录,打开cmd更新配置 npm i (i是install缩写) 最后引入,比如我

  • 详解 vue better-scroll滚动插件排坑

    BetterScroll号称目前最好用的移动端滚动插件,因此它的强大之处肯定是存在的.要不...哈哈.个人感觉还是很好用的.这篇文章不是笼统的讲 BetterScroll ,而是单讲滚动,想要深入了解它,请移步这里. 滚动原理 better-scroll 是什么滚动原理 better-scroll 是一款重点解决移动端(已支持 PC)各种滚动场景需求的插件.它的核心是借鉴的 iscroll 的实现,它的 API 设计基本兼容 iscroll,在 iscroll 的基础上又扩展了一些 featur

  • vue滚动插件better-scroll使用详解

    本文实例为大家分享了vue滚动插件better-scroll的具体代码,供大家参考,具体内容如下 1. 概述 1.1 说明 better-scroll是一款重点解决移动端(已支持PC)各种滚动场景需求的插件.例如淘宝聚划算中的类型选择(女装/家纺/生鲜美食等),没有滚动条显示却实现了滚动功能. 1.2 better-scroll安装 npm install better-scroll --save 安装至项目中 1.3 better-scroll使用 better-scroll常见应用场景(列表

  • 详解无限滚动插件vue-infinite-scroll源码解析

    最近在项目中遇到一个需求,有一个列表需要滚动加载,类似于微博的无限滚动.当时第一反应时监听滚动事件,在判断滚动到达底部时加载下一页,同时心里也清楚,监听滚动事件需要做好截流.顺手搜索了下发现有一个现成的插件vue-infinite-scroll,用法也很简单,于是乎就用了起来. 需求上线后,对它的实现挺好奇的,于是研究了一番源码,这篇文章就是源码解析笔记. 插件使用方法 这是一个 vue 的指令,按照 github 仓库上的介绍,用法挺简单的,例如: <div class="app&quo

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

    目录 起步 调研 实现思路 工程构建 代码实现 video实现 slide.vue 组合使用 视频自动播放问题 git地址 总结 起步 年前单位需要搞一个类似抖音的需求,这本应是客户端的任务,然而,不知天高地厚的我却接了下来,然而下细致调研之下,发现网上并没有成熟的方案,但是却又很多需求,各大论坛全是提问的帖子,却少有人回答和解决. 这一瞬间,俺慌了,毕竟单位的活,排期都是定死的,这时候临阵退缩,实乃下下策.于是只能撸起袖子加油干.毕竟自己揽的事,含着泪也要干完,这就是男人,一个吐沫一个钉! 调

  • 浅谈Vue3.0新版API之composition-api入坑指南

    关于VUE3.0 由于vue3.0语法跟vue2.x的语法几乎是完全兼容的,本文主要介绍了如何使用composition-api,主要分以下几个方面来讲 使用vite体验vue3.0 composition-api解决了什么问题 语法糖介绍 vite的安装使用 vite仓库地址 https://github.com/vuejs/vite 上面有详细的安装使用教程,按照步骤安装即可 composition-api解决了什么问题 使用传统的option配置方法写组件的时候问题,随着业务复杂度越来越高

  • vue3中vuex与pinia的踩坑笔记记录

    目录 介绍 安装使用 简单对比写法差异与共同点 Vuex 和 Pinia 的优缺点 何时使用Pinia,何时使用Vuex 总结 介绍 Pinia 是 Vue.js 的轻量级状态管理库,最近很受欢迎.它使用 Vue 3 中的新反应系统来构建一个直观且完全类型化的状态管理库. Pinia的成功可以归功于其管理存储数据的独特功能(可扩展性.存储模块组织.状态变化分组.多存储创建等). 另一方面,Vuex也是为Vue框架建立的一个流行的状态管理库,它也是Vue核心团队推荐的状态管理库.Vuex高度关注应

  • Android中模仿抖音加载框之两颗小球转动效果

    安卓版抖音v2.5加载框: 效果图如下所示: 本控件效果图: 使用方法 源码地址:Android仿抖音加载框之两颗小球转动控件 1.xml引用: <com.douyinloadingview.DYLoadingView android:id="@+id/dy3" android:layout_width="match_parent" android:layout_height="wrap_content" android:backgroun

  • Intellij IDEA 旗舰版创建 Spring MVC 项目踩过的坑

    学生可以申请Intellij IDEA旗舰版免费试用!我终于可以暂时不用折腾社区版啦啦啦啦啦!!! IDEA旗舰版可以直接创建Spring MVC项目,但创建后的项目并不是直接就可以运行,还需要进行一些配置. 一.创建项目 打开Intellij IDEA,创建项目(CreateNewProject): 在左边选择 "Spring",然后右边勾选"Spring MVC",下面的"WebApplication"应该会自动勾选,如果没有,则手动勾选上:

  • 使用Flutter开发的抖音国际版实例代码详解

    简介 最近花了两天时间研究使用Flutter开发一个抖音国际版. 个人感觉使用Flutter开发app快得不要不要的额. 两天就基本可以开发个大概出来. 最主要是热重载,太方便实时调整UI布局了. 相应速度极快. 如下图: 主要项目架构 详细说明一下,开发主要在lib文件夹 pubspec.yaml是配置插件的位置,如http: ^0.12.0+4,类似依赖组件. common文件夹存放的是重写的网络组件,以及图标组件icons.dart config文件夹存放的api.dart,wei调用的a

  • Android仿抖音上下滑动布局

    抖音上下滑动,监听播放,自动吸顶,吸底效果,供大家参考,具体内容如下 使用RecyclerView+PagerSnapHelper实现 public class DouYinLayoutManager extends LinearLayoutManager implements RecyclerView.OnChildAttachStateChangeListener{ //判断是否上滑还是下滑 private int mDrift; private OnViewPagerListener on

  • 我喜欢你 抖音表白程序python版

    本文实例为大家分享了python抖音表白神器,供大家参考,具体内容如下 # -*- coding: utf-8 -*- import sys from PyQt5 import QtWidgets from PyQt5.QtGui import QFont,QIcon#QtWidgets不包含QFont必须调用QtGui from PyQt5 import QtGui,QtCore import random class MessageBox(QtWidgets.QWidget):#继承自父类Q

  • 使用vue2.6实现抖音【时间轮盘】屏保效果附源码

    写在前面: 前段时间看抖音,有人用时间轮盘作为动态的桌面壁纸,一时间成为全网最火的电脑屏保,后来小米等运用市场也出现了[时间轮盘],有点像五行八卦,感觉很好玩,于是突发奇想,自己写一个网页版小DEMO玩玩,先看看效果: 当然实现这个效果,前端的角度来说,有很多,这里介绍最简单的,达到这个效果纯粹是元素圆性布局,如果仅仅是这样肯定没有达到各位老铁心理需求,所以既然,做了肯定是要做一个麻雀虽小五脏俱全的小demo,于是就把vue全家桶用上带设置的小项目.接下来就一步一步带各位从0到1构建这个小东西.

  • 基于vue+uniapp直播项目实现uni-app仿抖音/陌陌直播室功能

    一.项目简介 uni-liveShow是一个基于vue+uni-app技术开发的集小视频/IM聊天/直播等功能于一体的微直播项目.界面仿制抖音|火山小视频/陌陌直播,支持编译到多端(H5.小程序.App端) 且兼容效果一致. 二.效果预览 在H5.小程序.App端测试效果如下:(后续大图均为APP端) 三.使用技术 编码器+技术:HBuilderX + vue/NVue/uniapp/vuex iconfont图标:阿里字体图标库 自定义导航栏 + 底部Tabbar 弹窗组件:uniPop(un

随机推荐