更新记录

1.1.1(2024-10-16)

  • 提供 vue2 代码示例

1.0.0(2024-10-13)

仿抖音短视频(超高性能)H5、APP、小程序全端支持,支持 m3u8;该插件支持扩展和自定义,插件内有详细注释说明


平台兼容性

Vue2 Vue3
App 快应用 微信小程序 支付宝小程序 百度小程序 字节小程序 QQ小程序
HBuilderX 4.0 app-vue app-nvue × × × × ×
钉钉小程序 快手小程序 飞书小程序 京东小程序
× × × ×
H5-Safari Android Browser 微信浏览器(Android) QQ浏览器(Android) Chrome IE Edge Firefox PC-Safari

组件版本说明

ml-swiper-v2、ml-swiper-v3 均支持 VUE2、VUE3

下文提供了 vue3 版本代码示例,末尾提供了 vue2 版本代码示例

特别说明:

  • ml-swiper-v2、ml-swiper-v3 后面的 -v{n} 仅表示 ml-swiper 组件的版本 并不代表 vue 的版本
  • ml-swiper-v2、ml-swiper-v3 后面的 -v{n} 仅表示 ml-swiper 组件的版本 并不代表 vue 的版本
  • ml-swiper-v2、ml-swiper-v3 后面的 -v{n} 仅表示 ml-swiper 组件的版本 并不代表 vue 的版本
  • ml-swiper-v2、ml-swiper-v3 均支持 VUE2、VUE3

扩展或自定义


扩展或自定义 请阅读本插件中的:抖音官方示例.md

无需扩展和自定义 推荐使用:ml-swiper-v3



组件支持情况

ml-swiper-v2版本:支持 VUE2、VUE3

H5:支持 m3u8 流媒体资源




组件其他版本

更多组件 请前往 作者主页查看 :https://ext.dcloud.net.cn/publisher?id=1784252

ml-swiper :该版本的插件 为 ml-swiper 的第一版 插件 适用于 案例测试、组件学习、抖音功能分析等;该版本的插件 仅支持 VUE3;


ml-swiper-v2 :该 插件 是在 ml-swiper 版本上做的升级,支持 VUE2、VUE3,并且支持了 m3u8 流媒体资源;此版本的插件 适用于 个人或者公司 的二次开发 定制专属插件;插件内 功能、方法 均有详细注释说明,并且 组件内详细说明了 抖音APP 的实现逻辑,以及抖音官方的说明;(通过该 版本的组件 可以实现一个 适用于自己或者公司 专属的 扩展组件)


ml-swiper-v3 :该 插件 是在 ml-swiper-v2 的基础上 做出的最终版,并且优化了 大资源数据下 导致的上下滑动卡顿的问题。该组件不需要特殊配置,容易上手,内置插槽 便于实现个性化UI页面,并且提供了 很多组件方法 便于自定义实现更多的业务逻辑;




扩展或自定义


扩展或自定义 请阅读本插件中的:抖音官方示例.md

无需扩展和自定义 推荐使用:ml-swiper-v3




真实运行示例


APP-雷电模拟器




手机端-QQ浏览器




组件参数props

属性名 类型 默认值 说明 必须
list Array [] 视频数据,默认空数组,参数详情下见options介绍
width String 100% 组件宽度,默认与设备同宽
height String 100% 组件高度,默认与设备同高
criticalVal Number 2 临界值,当 list.length - currentIndex >= criticalVal时触发加载更多事件
progress Boolean true 是否显示进度条,默认显示进度条
duration Boolean true 是否显示播放时间,默认显示播放时长


视频数据详情options

属性名 类型 默认值 说明 必须
url String "" 视频资源地址
title String "" 视频标题
poster String "" 视频封面,支持JPG、PNG等常见图片文件
----- ----- 其他属性可根据需要自定义 ----- -----
比如: userInfo = {} 用户相关数据
比如: commentList = [] 评论数据列表
比如: date 视频发布时间
比如: location IP属地信息
。。。 。。。 。。。 。。。 。。。

组件事件Events

事件 参数 解释 说明
onchange (event)
滑动事件
event = {index, context, video} index:当前视频的索引
context:video的上下文对象
video:正在播放的视频数据
当视频上下滑动时,触发 onchange事件
onplay (event)
播放事件
event = {index, context, video} index:当前视频的索引
context:video的上下文对象
video:正在播放的视频数据
当前视频播放时,触发 onplay事件
onpause (event)
暂停事件
event = {index, context, video} index:当前视频的索引
context:video的上下文对象
video:正在播放的视频数据
当前视频暂停时,触发 onpause事件
onended (event)
结束事件
event = {index, context, video} index:当前视频的索引
context:video的上下文对象
当前视频播放结束时,触发 onpause事件
ontimeupdate (event)
进度变更事件
event事件 event:APP、小程序、H5 原生事件 视频进度条变化时触发ontimeupdate事件,可用来自定义进度条
onwaiting (event)
出现缓冲事件
event = {index, context, video} index:当前视频的索引
context:video的上下文对象
当前视频播放出现缓冲时,触发 onwaiting事件
onerror (event)
播放出错事件
event = {index, context, video, error, event} index:当前视频的索引
context:video的上下文对象
当前视频播放出错时,触发 onerror事件
onclick (event)
视频单击事件
event = {index, context, video, playing} index:当前视频的索引
context:video的上下文对象
video:当前视频的数据信息
playing:是否正在播放
当单击视频时触发onclick事件
ondblclick (event)
视频双击事件
event = {index, context, video, playing} index:当前视频的索引
context:video的上下文对象
video:当前视频的数据信息
当双击视频时触发ondblclick事件
loadmore(event)
加载更多数据
event = { index } index:当前视频的索引 list.length - currentIndex >= criticalVal时触发加载更多事件

组件方法methods

事件 参数 说明
fullScreen() 进入全屏 - 全屏播放
exitFullScreen() 退出全屏 - 退出全屏播放
setRate(rate) 设置倍速 Number: 0.5 | 0.8 | 1.0 | 1.25 | 1.5 | 2.0 当前视频倍速,不同平台支持不一样
setSeek(val) 指定播放时间 Number: 60 指定播放时间,单位秒,从 60秒处 播放


方法使用示例

<!-- ...省略... -->
<ml-swiper-v2 ref="mlSwiperRef"></ml-swiper-v2>
<!-- ...省略... -->
<script setup>
  import { ref } from 'vue';
  const mlSwiperRef = ref(null);

  // ...省略...
  /**
   * 全屏
   */
  function fullScreen() {
    mlSwiperRef.value?.fullScreen();
    // context?.requestFullScreen();
  }

  /**
   * 退出全屏
   */
  function exitFullScreen() {
    mlSwiperRef.value?.exitFullScreen();
    // context?.exitFullScreen();
  }

  /**
   * 倍速
   * @param {Number} rate 0.5 | 0.8 | 1.0 | 1.25 | 1.5 | 2.0
   */
  function setRate(rate) {
    mlSwiperRef.value?.setRate(rate);
    // context?.playbackRate(rate);
  }

  /**
   * 跳转到指定位置播放
   * @param {Number} val 单位秒
   */
  function setSeek(val) {
    mlSwiperRef.value?.setSeek(val);
    // context?.seek(val);
  }
  /// ...省略...
</script>



扩展或自定义


扩展或自定义 请阅读本插件中的:抖音官方示例.md

无需扩展和自定义 推荐使用:ml-swiper-v3



vue3 版本示例代码

组件示例代码

微信小程序 和 H5 网页端示例代码(注:H5端 需要安装 hls,详见下文: H5端使用说明

# H5 端 安装插件
npm install hls.js

index.vue

<template>
  <template v-if="type == 'v2'">
    <!-- 这里使用 v-if 是为了防止 请求后台时还没返回数据 导致组件初始化时没有视频资源 而出现错误 -->
    <ml-swiper-v2 v-if="lists && lists.length > 0" :list="lists" @loadmore="loadmore" @ondblclick="" >
      <!-- 自定义内容,这里设置100%后 层级高于video,会导致单击、双击功能失效 -->
      <template v-slot:default="{ index }">
        <view class="body">
          <text class="text">{{ index + 1 }} / {{ lists.length }}</text>
        </view>
      </template>
      <!-- 右侧 -->
      <template v-slot:right="{ item }">
        <view class="right">
          <image class="userAvatar" :src="item?.author?.avatar" @click="avatarClick"></image>
          <!-- 喜欢 -->
          <view class="icon">
            <uni-icons type="heart-filled" size="35" :color="datas[0]?.includes(item.videoId) ? '#ff0004' : '#fff'" @tap="iconClick(0, item.videoId)" />
            <text class="icon-val">666</text>
          </view>
          <!-- 评论 -->
          <view class="icon">
            <uni-icons type="chat-filled" size="35" color="#fff" @tap="comment(item.videoId)" />
            <text class="icon-val">668</text>
          </view>
          <!-- 收藏 -->
          <view class="icon">
            <uni-icons type="star-filled" size="35" :color="datas[1]?.includes(item.videoId) ? '#ff0' : '#fff'" @tap="iconClick(1, item.videoId)" />
            <text class="icon-val">888</text>
          </view>
          <!-- 转发 -->
          <view class="icon">
            <uni-icons type="redo-filled" size="35" color="#fff" @tap="forward(item.videoId)" />
            <text class="icon-val">999</text>
          </view>
        </view>
      </template>
      <!-- 底部 -->
      <template v-slot:bottom="{ item }">
        <view class="bottom">
          <text class="title">{{ item?.title }}</text>
        </view>
      </template>
    </ml-swiper-v2>
  </template>

  <template v-if="type == 'v3'">
    <ml-swiper-v3 v-if="lists && lists.length > 0" :list="lists" @loadmore="loadmore" @ondblclick="" >
      <!-- 自定义内容,这里设置100%后 层级高于video,会导致单击、双击功能失效 -->
      <template v-slot:default="{ index }">
        <view class="body">
          <text class="text">{{ index + 1 }} / {{ lists.length }}</text>
        </view>
      </template>
      <!-- 右侧 -->
      <template v-slot:right="{ item }">
        <view class="right">
          <image class="userAvatar" :src="item?.author?.avatar" @click="avatarClick"></image>
          <!-- 喜欢 -->
          <view class="icon">
            <uni-icons type="heart-filled" size="35" :color="datas[0]?.includes(item.videoId) ? '#ff0004' : '#fff'" @tap="iconClick(0, item.videoId)" />
            <text class="icon-val">666</text>
          </view>
          <!-- 评论 -->
          <view class="icon">
            <uni-icons type="chat-filled" size="35" color="#fff" @tap="comment(item.videoId)" />
            <text class="icon-val">668</text>
          </view>
          <!-- 收藏 -->
          <view class="icon">
            <uni-icons type="star-filled" size="35" :color="datas[1]?.includes(item.videoId) ? '#ff0' : '#fff'" @tap="iconClick(1, item.videoId)" />
            <text class="icon-val">888</text>
          </view>
          <!-- 转发 -->
          <view class="icon">
            <uni-icons type="redo-filled" size="35" color="#fff" @tap="forward(item.videoId)" />
            <text class="icon-val">999</text>
          </view>
        </view>
      </template>
      <!-- 底部 -->
      <template v-slot:bottom="{ item }">
        <view class="bottom">
          <text class="title">{{ item?.title }}</text>
        </view>
      </template>
    </ml-swiper-v3>
  </template>
</template>

<script setup>
  import { onMounted, reactive, ref } from 'vue';

  const lists = ref([]);
  const type = ref("v2"); // 当前 ml-swiper版本 (当前版本是 ml-swiper-v2)
  const datas = reactive({});

  let count = 0;

  function avatarClick() {
    uni.showToast({ title: "点击头像", icon: "none", duration: 1000 });
  }

  function comment(_id) {
    uni.showToast({ title: "查看评论", icon: "none", duration: 1000 });
  }

  function forward(_id) {
    uni.showToast({ title: "转发", icon: "none", duration: 1000 });
  }

  function iconClick(type, id) {
    datas[type] = (datas[type] || []);
    let index = datas[type]?.indexOf(id);
    if (index >= 0) {
      datas[type].splice(index, 1);
    } else {
      datas[type].push(id);
    }
  }

  function ({ video }) {
    console.log(video?.videoId);
    iconClick(0, video?.videoId);
  }

  function loadmore() {
    uni.showToast({ title: "加载更多", icon: "none", duration: 1000 });
    for (var i = 0; i < 2; i++) {
      getList().forEach((item) => {
        count = count + 1;
        item.title = `【${count}】` + item.title;
        lists.value.push(item);
      });
    }
  }

  onMounted(() => {
    // 这里直接生成 200 条视频数据进行测试(模拟请求后台获取数据)
    uni.showToast({ title: "加载中...", icon: "loading", duration: 1500 });
    setTimeout(() => {
      let size = type.value == 'v2' ? 2 : 50;
      for (var i = 0; i < size; i++) {
        getList().forEach((item) => {
          count = count + 1;
          item.title = `【${count}】` + item.title;
          lists.value.push(item);
        });
      }
      uni.hideToast();
    }, 1500);
  });

  const getList = () => {
    return [
      {
        videoId: lists.value.length + 1,
        title: `抖音美女主播,JK超短裙学生妆美女跳舞展示,爱了爱了。`,
        // poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
        url: "https://txmov2.a.yximgs.com/upic/2020/11/08/19/BMjAyMDExMDgxOTQxNTlfNTIzNDczMzQ0XzM4OTQ1MDk5MTI4XzFfMw==_b_Bc770a92f0cf153407d60a2eddffeae2a.mp4",
        uploadTime: "2023-11-08 19:41",
        ipLocation: "上海",
        author: {
          authorId: 101,
          avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
          nickName: "陌路",
          genderName: "男"
        }
      },
      {
        videoId: lists.value.length + 2,
        title: `御姐美女抖音作品,来个自拍视频把,好美啊。`,
        // poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
        url: "https://txmov2.a.yximgs.com/upic/2020/10/02/09/BMjAyMDEwMDIwOTAwMDlfMTIyMjc0NTk0Ml8zNjk3Mjg0NjcxOF8xXzM=_b_B28a4518e86e2cf6155a6c1fc9cf79c6d.mp4",
        uploadTime: "2023-10-02 09:41",
        ipLocation: "贵州",
        author: {
          authorId: 102,
          avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
          nickName: "御姐呀",
          genderName: "女"
        }
      },
      {
        videoId: lists.value.length + 3,
        title: `抖音主播可爱妹子新学的舞蹈,超可爱的美女主播。`,
        // poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
        url: "https://txmov6.a.yximgs.com/upic/2020/08/23/00/BMjAyMDA4MjMwMDMyNDRfMTYzMzY5MDA0XzM0ODI4MDcyMzQ5XzFfMw==_b_B9a1c9d4e3a090bb2815994d7f33a906a.mp4",
        uploadTime: "2023-08-23 00:41",
        ipLocation: "广州",
        author: {
          authorId: 103,
          avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
          nickName: "野花猫",
          genderName: "女"
        }
      },
      {
        videoId: lists.value.length + 4,
        title: `多个美女带着遮阳帽出去散步自拍视频,好好看。`,
        // poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
        url: "https://alimov2.a.yximgs.com/upic/2020/07/02/14/BMjAyMDA3MDIxNDUyMDlfOTExMjIyMjRfMzE1OTEwNjAxNTRfMV8z_b_Bf3005d42ce9c01c0687147428c28d7e6.mp4",
        uploadTime: "2023-07-02 14:41",
        ipLocation: "山西",
        author: {
          authorId: 104,
          avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
          nickName: "蓝姬",
          genderName: "女"
        }
      }
    ];
  }
</script>

<style scoped lang="scss">
  .body {
    position: absolute;
    top: 0;
    margin: 0 auto;
  }

  .text {
    font-size: 50px;
    color: #ff5918;
  }

  .right {
    width: 50px;
    padding: 10px 0;
    margin: 0 auto;
  }

  .icon {
    display: flex;
    flex-wrap: wrap;
    flex-direction: column;
    justify-content: center;
    margin: 8px auto;
  }

  .icon-val {
    color: #fff;
    font-size: 13px;
    text-align: center;
  }

  .userAvatar {
    width: 40px;
    height: 40px;
    border-radius: 100%;
  }

  .bottom {
    margin: 10px;
  }

  .title {
    color: #fff;
    font-size: 13px;
  }
</style>



H5端使用说明

如果需要编译为 H5 端,则需要安装插件 hls,APP端 和 微信小程序 不需要安装

npm install hls.js

测试说明:H5 端测试了 QQ浏览器 支持良好,夸克浏览器 不支持该插件(夸克浏览器只支持内置播放器且悬浮于顶层)。 其他浏览器均为测试,如需使用 请自行测试。



APP代码示例

app端

APP端开发,暂不支持 在 vue 页面中使用 ml-swiper-v3 组件,如需开发 APP 应用,请将 vue 页面 改为 nvue 页面,其他不变即可正常使用该组件。

特别说明:APP 端 不支持动画效果的 progress 进度条,其他功能依然可以正常使用

index.nvue

<template>
  <!-- 这里使用 v-if 是为了防止 请求后台时还没返回数据 导致组件初始化时没有视频资源 而出现错误 -->
  <ml-swiper-v2 v-if="lists && lists.length > 0" :list="lists" @loadmore="loadmore" @ondblclick="" >
    <!-- 右侧 -->
    <template v-slot:right="{ item }">
      <view class="right">
        <image class="userAvatar" :src="item?.author?.avatar" @click="avatarClick"></image>
        <!-- 喜欢 -->
        <view class="icon">
          <uni-icons type="heart-filled" size="35" :color="datas[0]?.includes(item.videoId) ? '#ff0004' : '#fff'" @tap="iconClick(0, item.videoId)" />
          <text class="icon-val">666</text>
        </view>
        <!-- 评论 -->
        <view class="icon">
          <uni-icons type="chat-filled" size="35" color="#fff" @tap="comment(item.videoId)" />
          <text class="icon-val">668</text>
        </view>
        <!-- 收藏 -->
        <view class="icon">
          <uni-icons type="star-filled" size="35" :color="datas[1]?.includes(item.videoId) ? '#ff0' : '#fff'" @tap="iconClick(1, item.videoId)" />
          <text class="icon-val">888</text>
        </view>
        <!-- 转发 -->
        <view class="icon">
          <uni-icons type="redo-filled" size="35" color="#fff" @tap="forward(item.videoId)" />
          <text class="icon-val">999</text>
        </view>
      </view>
    </template>
    <!-- 底部 -->
    <template v-slot:bottom="{ item }">
      <view class="bottom">
        <text class="title">{{ item?.title }}</text>
      </view>
    </template>
  </ml-swiper-v2>
</template>
<script setup >
  import { onMounted, reactive, ref } from 'vue';

  const lists = ref([]);
  const type = ref("v3");
  const datas = reactive({});

  let count = 0;

  function avatarClick() {
    uni.showToast({ title: "点击头像", icon: "none", duration: 1000 });
  }

  function comment(_id) {
    uni.showToast({ title: "查看评论", icon: "none", duration: 1000 });
  }

  function forward(_id) {
    uni.showToast({ title: "转发", icon: "none", duration: 1000 });
  }

  function iconClick(type, id) {
    datas[type] = (datas[type] || []);
    let index = datas[type]?.indexOf(id);
    if (index >= 0) {
      datas[type].splice(index, 1);
    } else {
      datas[type].push(id);
    }
  }

  function ({ video }) {
    console.log(video?.videoId);
    iconClick(0, video?.videoId);
  }

  function loadmore() {
    uni.showToast({ title: "加载更多", icon: "none", duration: 1000 });
    for (var i = 0; i < 2; i++) {
      getList().forEach((item) => {
        count = count + 1;
        item.title = `【${count}】` + item.title;
        lists.value.push(item);
      });
    }
  }

  onMounted(() => {
    // 这里直接生成 200 条视频数据进行测试(模拟请求后台获取数据)
    uni.showToast({ title: "加载中...", icon: "loading", duration: 1500 });
    setTimeout(() => {
      let size = type.value == 'v2' ? 2 : 50;
      for (var i = 0; i < size; i++) {
        getList().forEach((item) => {
          count = count + 1;
          item.title = `【${count}】` + item.title;
          lists.value.push(item);
        });
      }
      uni.hideToast();
    }, 1500);
  });

  const getList = () => {
    return [
      {
        videoId: lists.value.length + 1,
        title: `抖音美女主播,JK超短裙学生妆美女跳舞展示,爱了爱了。`,
        // poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
        url: "https://txmov2.a.yximgs.com/upic/2020/11/08/19/BMjAyMDExMDgxOTQxNTlfNTIzNDczMzQ0XzM4OTQ1MDk5MTI4XzFfMw==_b_Bc770a92f0cf153407d60a2eddffeae2a.mp4",
        uploadTime: "2023-11-08 19:41",
        ipLocation: "上海",
        author: {
          authorId: 101,
          avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
          nickName: "陌路",
          genderName: "男"
        }
      },
      {
        videoId: lists.value.length + 2,
        title: `御姐美女抖音作品,来个自拍视频把,好美啊。`,
        // poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
        url: "https://txmov2.a.yximgs.com/upic/2020/10/02/09/BMjAyMDEwMDIwOTAwMDlfMTIyMjc0NTk0Ml8zNjk3Mjg0NjcxOF8xXzM=_b_B28a4518e86e2cf6155a6c1fc9cf79c6d.mp4",
        uploadTime: "2023-10-02 09:41",
        ipLocation: "贵州",
        author: {
          authorId: 102,
          avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
          nickName: "御姐呀",
          genderName: "女"
        }
      },
      {
        videoId: lists.value.length + 3,
        title: `抖音主播可爱妹子新学的舞蹈,超可爱的美女主播。`,
        // poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
        url: "https://txmov6.a.yximgs.com/upic/2020/08/23/00/BMjAyMDA4MjMwMDMyNDRfMTYzMzY5MDA0XzM0ODI4MDcyMzQ5XzFfMw==_b_B9a1c9d4e3a090bb2815994d7f33a906a.mp4",
        uploadTime: "2023-08-23 00:41",
        ipLocation: "广州",
        author: {
          authorId: 103,
          avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
          nickName: "野花猫",
          genderName: "女"
        }
      },
      {
        videoId: lists.value.length + 4,
        title: `多个美女带着遮阳帽出去散步自拍视频,好好看。`,
        // poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
        url: "https://alimov2.a.yximgs.com/upic/2020/07/02/14/BMjAyMDA3MDIxNDUyMDlfOTExMjIyMjRfMzE1OTEwNjAxNTRfMV8z_b_Bf3005d42ce9c01c0687147428c28d7e6.mp4",
        uploadTime: "2023-07-02 14:41",
        ipLocation: "山西",
        author: {
          authorId: 104,
          avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
          nickName: "蓝姬",
          genderName: "女"
        }
      }
    ];
  }
</script>
<style scoped lang="scss">
  .right {
    width: 50px;
    padding: 10px 0;
    margin: 0;
  }

  .userAvatar {
    width: 40px;
    height: 40px;
    border-radius: 100;
  }

  .icon {
    display: flex;
    flex-wrap: wrap;
    flex-direction: column;
    justify-content: center;
    margin: 8px 0;
  }

  .icon-val {
    color: #fff;
    font-size: 13px;
    text-align: center;
  }

  .bottom {
    margin: 10px;
  }

  .title {
    color: #fff;
    font-size: 13px;
  }
</style>



完整参数事件

以下是 ml-swiper-v3 中 所有的 参数、事件和方法的完整示例

<template>
  <ml-swiper-v2 
    v-if="lists && lists.length > 0"
    ref="mlSwiperRef"
    :list="lists"
    :width="width"
    :height="height"
    :criticalVal="criticalVal"
    :progress="progress"
    :duration="duration"
    @onchange="onchange"
    @loadmore="loadmore"
    @onclick="onclick"
    @ondblclick=""
    @onplay="onplay"
    @onpause="onpause"
    @onended=""
    @ontimeupdate=""
    @onwaiting=""
    @onerror="onerror"
  >
    <!-- 自定义内容,这里设置100%后 层级高于video,会导致单击、双击功能失效 -->
    <template v-slot:default="{ index }">
      <view class="body">
        <text class="text">{{ index + 1 }} / {{ lists.length }}</text>
      </view>
    </template>
    <!-- 右侧 -->
    <template v-slot:right="{ item }">
      <view class="right">
        <image class="userAvatar" :src="item?.author?.avatar" @click="avatarClick"></image>
        <!-- 喜欢 -->
        <view class="icon">
          <uni-icons type="heart-filled" size="35" :color="datas[0]?.includes(item.videoId) ? '#ff0004' : '#fff'" @tap="iconClick(0, item.videoId)" />
          <text class="icon-val">666</text>
        </view>
        <!-- 评论 -->
        <view class="icon">
          <uni-icons type="chat-filled" size="35" color="#fff" @tap="comment(item.videoId)" />
          <text class="icon-val">668</text>
        </view>
        <!-- 收藏 -->
        <view class="icon">
          <uni-icons type="star-filled" size="35" :color="datas[1]?.includes(item.videoId) ? '#ff0' : '#fff'" @tap="iconClick(1, item.videoId)" />
          <text class="icon-val">888</text>
        </view>
        <!-- 转发 -->
        <view class="icon">
          <uni-icons type="redo-filled" size="35" color="#fff" @tap="forward(item.videoId)" />
          <text class="icon-val">999</text>
        </view>
      </view>
    </template>
    <!-- 底部 -->
    <template v-slot:bottom="{ item }">
      <view class="bottom">
        <text class="title">{{ item?.title }}</text>
      </view>
    </template>
  </ml-swiper-v2>
</template>

<script setup>
  import { onMounted, reactive, ref } from 'vue';

  const lists = ref([]); // 视频资源列表 [{url: "资源链接必须", title: "标题", poster: "封面"}]
  const windowInfo = uni.getWindowInfo();
  const width = ref(windowInfo.windowWidth + 'px'); // 组件宽度(不写默认设备同宽)
  const height = ref(windowInfo.windowHeight + 'px'); // 组件高度(不写默认设备同高)
  const criticalVal = ref(2); // 临界值(当观看到倒数 2 个视频时,触发加载更多事件,不写默认 2)
  const progress = ref(true); // 进度条(不写默认 true,APP 端不显示进度动画)
  const duration = ref(true); // 播放时间(不写默认 true,显示播放时间)
  const mlSwiperRef = ref(null);
  const datas = reactive({});
  let count = 0;
  let context = null;

  onMounted(() => {
    // 这里直接生成 200 条视频数据进行测试
    for (var i = 0; i < 50; i++) {
      getList().forEach((item) => {
        count = count + 1;
        item.title = `【${count}】` + item.title;
        lists.value.push(item);
      });
    }
  });

  function avatarClick() {
    uni.showToast({ title: "点击头像", icon: "none", duration: 1000 });
  }

  function comment(_id) {
    uni.showToast({ title: "查看评论", icon: "none", duration: 1000 });
    fullScreen();
  }

  function forward(_id) {
    uni.showToast({ title: "转发", icon: "none", duration: 1000 });
  }

  function iconClick(type, id) {
    datas[type] = (datas[type] || []);
    let index = datas[type]?.indexOf(id);
    if (index >= 0) {
      datas[type].splice(index, 1);
    } else {
      datas[type].push(id);
    }
  }

  function onchange(e) {
    console.log("滑动事件:", e);
  }

  /**
   * 加载更多数据(请求后台 获取更多数据)
   */
  function loadmore() {
    uni.showToast({ title: "加载更多", icon: "none", duration: 1000 });
    for (var i = 0; i < 2; i++) {
      getList().forEach((item) => {
        count = count + 1;
        item.title = `【${count}】` + item.title;
        lists.value.push(item);
      });
    }
  }

  function onclick(e) {
    console.log("单击事件:", e);
  }

  function (e) {
    console.log("双击事件:", e);
  }

  function onplay(e) {
    console.log("播放事件:", e);
    context = e.context;
  }

  function onpause(e) {
    console.log("暂停事件:", e);
  }

  function (e) {
    console.log("结束事件:", e);
  }

  function (e) {
    // console.log("加载事件:", e);
  }

  function (e) {
    console.log("缓冲事件:", e);
  }

  function onerror(e) {
    console.log("报错事件:", e);
  }

  /**
   * 全屏
   */
  function fullScreen() {
    uni.showToast({ title: "全屏", icon: "none", duration: 1000 });
    mlSwiperRef.value?.fullScreen();
    // context?.requestFullScreen();
  }

  /**
   * 退出全屏
   */
  function exitFullScreen() {
    uni.showToast({ title: "退出全屏", icon: "none", duration: 1000 });
    mlSwiperRef.value?.exitFullScreen();
    // context?.exitFullScreen();
  }

  /**
   * 倍速
   * @param {Number} rate 0.5 | 0.8 | 1.0 | 1.25 | 1.5 | 2.0
   */
  function setRate(rate) {
    uni.showToast({ title: "倍速" + rate, icon: "none", duration: 1000 });
    mlSwiperRef.value?.setRate(rate);
    // context?.playbackRate(rate);
  }

  /**
   * 跳转到指定位置播放
   * @param {Number} val 单位秒
   */
  function setSeek(val) {
    mlSwiperRef.value?.setSeek(val);
    // context?.seek(val);
  }

  const getList = () => {
    return [
      {
        videoId: lists.value.length + 1,
        title: `抖音美女主播,JK超短裙学生妆美女跳舞展示,爱了爱了。`,
        poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
        url: "https://txmov2.a.yximgs.com/upic/2020/11/08/19/BMjAyMDExMDgxOTQxNTlfNTIzNDczMzQ0XzM4OTQ1MDk5MTI4XzFfMw==_b_Bc770a92f0cf153407d60a2eddffeae2a.mp4",
        uploadTime: "2023-11-08 19:41",
        ipLocation: "上海",
        author: {
          authorId: 101,
          avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
          nickName: "陌路",
          genderName: "男"
        }
      },
      {
        videoId: lists.value.length + 2,
        title: `御姐美女抖音作品,来个自拍视频把,好美啊。`,
        poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
        url: "https://txmov2.a.yximgs.com/upic/2020/10/02/09/BMjAyMDEwMDIwOTAwMDlfMTIyMjc0NTk0Ml8zNjk3Mjg0NjcxOF8xXzM=_b_B28a4518e86e2cf6155a6c1fc9cf79c6d.mp4",
        uploadTime: "2023-10-02 09:41",
        ipLocation: "贵州",
        author: {
          authorId: 102,
          avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
          nickName: "御姐呀",
          genderName: "女"
        }
      },
      {
        videoId: lists.value.length + 3,
        title: `抖音主播可爱妹子新学的舞蹈,超可爱的美女主播。`,
        poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
        url: "https://txmov6.a.yximgs.com/upic/2020/08/23/00/BMjAyMDA4MjMwMDMyNDRfMTYzMzY5MDA0XzM0ODI4MDcyMzQ5XzFfMw==_b_B9a1c9d4e3a090bb2815994d7f33a906a.mp4",
        uploadTime: "2023-08-23 00:41",
        ipLocation: "广州",
        author: {
          authorId: 103,
          avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
          nickName: "野花猫",
          genderName: "女"
        }
      },
      {
        videoId: lists.value.length + 4,
        title: `多个美女带着遮阳帽出去散步自拍视频,好好看。`,
        poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
        url: "https://alimov2.a.yximgs.com/upic/2020/07/02/14/BMjAyMDA3MDIxNDUyMDlfOTExMjIyMjRfMzE1OTEwNjAxNTRfMV8z_b_Bf3005d42ce9c01c0687147428c28d7e6.mp4",
        uploadTime: "2023-07-02 14:41",
        ipLocation: "山西",
        author: {
          authorId: 104,
          avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
          nickName: "蓝姬",
          genderName: "女"
        }
      }
    ];
  }
</script>

<style scoped lang="scss">
  .body {
    position: absolute;
    top: 0;
    margin: 0 auto;
  }

  .text {
    font-size: 50px;
    color: #ff5918;
  }

  .right {
    width: 50px;
    padding: 10px 0;
    margin: 0 auto;
  }

  .icon {
    display: flex;
    flex-wrap: wrap;
    flex-direction: column;
    justify-content: center;
    margin: 8px auto;
  }

  .icon-val {
    color: #fff;
    font-size: 13px;
    text-align: center;
  }

  .userAvatar {
    width: 40px;
    height: 40px;
    border-radius: 100%;
  }

  .bottom {
    margin: 10px;
  }

  .title {
    color: #fff;
    font-size: 13px;
  }
</style>


扩展或自定义


扩展或自定义 请阅读本插件中的:抖音官方示例.md

无需扩展和自定义 推荐使用:ml-swiper-v3

vue2 版本示例代码

<template>
  <view class="wh-full">
    <template v-if="type == 'v2'">
      <!-- 这里使用 v-if 是为了防止 请求后台时还没返回数据 导致组件初始化时没有视频资源 而出现错误 -->
      <ml-swiper-v2 v-if="lists && lists.length > 0" :list="lists" @loadmore="loadmore" @ondblclick="">
        <!-- 自定义内容,这里设置100%后 层级高于video,会导致单击、双击功能失效 -->
        <template v-slot:default="{ index }">
          <view class="body">
            <text class="text">{{ index + 1 }} / {{ lists.length }}</text>
          </view>
        </template>
        <!-- 右侧 -->
        <template v-slot:right="{ item }">
          <view class="right" v-show="item != null">
            <!-- 用户头像 -->
            <image v-show="item.author != null" class="userAvatar" :src="item.author.avatar" @click="avatarClick">
            </image>
            <!-- 点赞 -->
            <view class="icon">
              <uni-icons type="heart-filled" size="35" :color="datas[0].includes(item.videoId) ? '#ff0004' : '#fff'"
                @tap="iconClick(0, item.videoId)" />
              <text class="icon-val">666</text>
            </view>
            <!-- 评论 -->
            <view class="icon">
              <uni-icons type="chat-filled" size="35" color="#fff" @tap="comment(item.videoId)" />
              <text class="icon-val">668</text>
            </view>
            <!-- 收藏 -->
            <view class="icon">
              <uni-icons type="star-filled" size="35" :color="datas[1].includes(item.videoId) ? '#ff0' : '#fff'"
                @tap="iconClick(1, item.videoId)" />
              <text class="icon-val">888</text>
            </view>
            <!-- 转发 -->
            <view class="icon">
              <uni-icons type="redo-filled" size="35" color="#fff" @tap="forward(item.videoId)" />
              <text class="icon-val">999</text>
            </view>
          </view>
        </template>
        <!-- 底部 -->
        <template v-slot:bottom="{ item }">
          <view class="bottom" v-show="item != null">
            <text class="title">{{ item.title }}</text>
          </view>
        </template>
      </ml-swiper-v2>
    </template>
    <template v-if="type == 'v3'">
      <!-- 这里使用 v-if 是为了防止 请求后台时还没返回数据 导致组件初始化时没有视频资源 而出现错误 -->
      <ml-swiper-v3 v-if="lists && lists.length > 0" :list="lists" @loadmore="loadmore" @ondblclick="">
        <!-- 自定义内容,这里设置100%后 层级高于video,会导致单击、双击功能失效 -->
        <template v-slot:default="{ index }">
          <view class="body">
            <text class="text">{{ index + 1 }} / {{ lists.length }}</text>
          </view>
        </template>
        <!-- 右侧 -->
        <template v-slot:right="{ item }">
          <view class="right" v-show="item != null">
            <!-- 用户头像 -->
            <image v-show="item.author != null" class="userAvatar" :src="item.author.avatar" @click="avatarClick">
            </image>
            <!-- 点赞 -->
            <view class="icon">
              <uni-icons type="heart-filled" size="35" :color="datas[0].includes(item.videoId) ? '#ff0004' : '#fff'"
                @tap="iconClick(0, item.videoId)" />
              <text class="icon-val">666</text>
            </view>
            <!-- 评论 -->
            <view class="icon">
              <uni-icons type="chat-filled" size="35" color="#fff" @tap="comment(item.videoId)" />
              <text class="icon-val">668</text>
            </view>
            <!-- 收藏 -->
            <view class="icon">
              <uni-icons type="star-filled" size="35" :color="datas[1].includes(item.videoId) ? '#ff0' : '#fff'"
                @tap="iconClick(1, item.videoId)" />
              <text class="icon-val">888</text>
            </view>
            <!-- 转发 -->
            <view class="icon">
              <uni-icons type="redo-filled" size="35" color="#fff" @tap="forward(item.videoId)" />
              <text class="icon-val">999</text>
            </view>
          </view>
        </template>
        <!-- 底部 -->
        <template v-slot:bottom="{ item }">
          <view class="bottom" v-show="item != null">
            <text class="title">{{ item.title }}</text>
          </view>
        </template>
      </ml-swiper-v3>
    </template>
  </view>
</template>

<script>
  export default {
    data() {
      return {
        lists: [], // 资源数据
        datas: [], // 点赞、收藏等数据
        count: 0, // 计数器
        type: "v2" // 使用的组件版本,v2:ml-swiper-v2、v3:ml-swiper-v3
      }
    },
    onLoad() {
      // (模拟请求后台获取数据)
      uni.showToast({ title: "加载中...", icon: "loading", duration: 1500 });
      setTimeout(() => {
        let size = this.type == "v3" ? 50 : 2;
        for (var i = 0; i < size; i++) {
          this.getList().forEach((item) => {
            this.count = this.count + 1;
            item.title = `【${this.count}】` + item.title;
            this.lists.push(item);
          });
        }
        uni.hideToast();
      }, 1500);
    },
    methods: {
      avatarClick() {
        uni.showToast({ title: "点击头像", icon: "none", duration: 1000 });
      },
      comment(_id) {
        uni.showToast({ title: "查看评论", icon: "none", duration: 1000 });
      },
      forward(_id) {
        uni.showToast({ title: "转发", icon: "none", duration: 1000 });
      },
      iconClick(type, id) {
        this.datas[type] = (this.datas[type] || []);
        let index = this.datas[type]?.indexOf(id);
        if (index >= 0) {
          this.datas[type].splice(index, 1);
        } else {
          this.datas[type].push(id);
        }
      },
      loadmore() {
        uni.showToast({ title: "加载更多", icon: "none", duration: 1000 });
        for (var i = 0; i < 2; i++) {
          this.getList().forEach((item) => {
            this.count = this.count + 1;
            item.title = `【${this.count}】` + item.title;
            this.lists.push(item);
          });
        }
      },
      ({ video }) {
        console.log(video?.videoId);
        this.iconClick(0, video?.videoId);
      },
      getList() {
        return [
          {
            videoId: this.lists.length + 1,
            title: `抖音美女主播,JK超短裙学生妆美女跳舞展示,爱了爱了。`,
            // poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
            url: "https://txmov2.a.yximgs.com/upic/2020/11/08/19/BMjAyMDExMDgxOTQxNTlfNTIzNDczMzQ0XzM4OTQ1MDk5MTI4XzFfMw==_b_Bc770a92f0cf153407d60a2eddffeae2a.mp4",
            uploadTime: "2023-11-08 19:41",
            ipLocation: "上海",
            author: {
              authorId: 101,
              avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
              nickName: "陌路",
              genderName: "男"
            }
          },
          {
            videoId: this.lists.length + 2,
            title: `御姐美女抖音作品,来个自拍视频把,好美啊。`,
            // poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
            url: "https://txmov2.a.yximgs.com/upic/2020/10/02/09/BMjAyMDEwMDIwOTAwMDlfMTIyMjc0NTk0Ml8zNjk3Mjg0NjcxOF8xXzM=_b_B28a4518e86e2cf6155a6c1fc9cf79c6d.mp4",
            uploadTime: "2023-10-02 09:41",
            ipLocation: "贵州",
            author: {
              authorId: 102,
              avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
              nickName: "御姐呀",
              genderName: "女"
            }
          },
          {
            videoId: this.lists.length + 3,
            title: `抖音主播可爱妹子新学的舞蹈,超可爱的美女主播。`,
            // poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
            url: "https://txmov6.a.yximgs.com/upic/2020/08/23/00/BMjAyMDA4MjMwMDMyNDRfMTYzMzY5MDA0XzM0ODI4MDcyMzQ5XzFfMw==_b_B9a1c9d4e3a090bb2815994d7f33a906a.mp4",
            uploadTime: "2023-08-23 00:41",
            ipLocation: "广州",
            author: {
              authorId: 103,
              avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
              nickName: "野花猫",
              genderName: "女"
            }
          },
          {
            videoId: this.lists.length + 4,
            title: `多个美女带着遮阳帽出去散步自拍视频,好好看。`,
            // poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
            url: "https://alimov2.a.yximgs.com/upic/2020/07/02/14/BMjAyMDA3MDIxNDUyMDlfOTExMjIyMjRfMzE1OTEwNjAxNTRfMV8z_b_Bf3005d42ce9c01c0687147428c28d7e6.mp4",
            uploadTime: "2023-07-02 14:41",
            ipLocation: "山西",
            author: {
              authorId: 104,
              avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
              nickName: "蓝姬",
              genderName: "女"
            }
          }
        ];
      }
    }
  }
</script>

<style scoped>
  .body {
    position: absolute;
    top: 0;
    margin: 0 auto;
  }

  .text {
    font-size: 50px;
    color: #ff5918;
  }

  .right {
    width: 50px;
    padding: 10px 0;
    margin: 0 auto;
  }

  .icon {
    display: flex;
    flex-wrap: wrap;
    flex-direction: column;
    justify-content: center;
    margin: 8px auto;
  }

  .icon-val {
    color: #fff;
    font-size: 13px;
    text-align: center;
  }

  .userAvatar {
    width: 40px;
    height: 40px;
    border-radius: 100%;
  }

  .bottom {
    margin: 10px;
  }

  .title {
    color: #fff;
    font-size: 13px;
  }

  .wh-full {
    width: 100%;
    height: 100%;
    padding: 0;
    margin: 0 auto;
    overflow: hidden;
  }
</style>

隐私、权限声明

1. 本插件需要申请的系统权限列表:

APP端需要配置manifest.json -> App模块配置 -> 勾选VideoPlay(视频播放)。

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

使用中有什么不明白的地方,就向插件作者提问吧~ 我要提问