更新记录
1.1.1(2024-10-18)
- 提供 ml-swiper 源码版权
1.0.0(2024-06-25)
仿抖音短视频(超高性能)H5、APP、小程序全端支持
平台兼容性
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
可 0 配置,高性能、低代码、全端兼容
组件其他版本
更多组件 请前往 作者主页查看 :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页面,并且提供了 很多组件方法 便于自定义实现更多的业务逻辑;
APP端效果图
APP开发时,请仔细阅读本文档的 APP开发说明
微信小程序端效果图
微信小程序开发时 直接在 vue页面中 引入该插件,不需要配置 复制下方 示例代码 即可运行
Vue网页端效果图
网页端开发时 直接在 vue页面中 引入该插件,不需要配置 复制下方 示例代码 即可运行
ml-swiper介绍
- 仿抖音短视频(超高性能)H5、APP、小程序全端支持。
- 可简单 0 配置,仿抖音短视频,丝滑切换视频效果 ,无限数据加载不卡顿。
- 插件下载即用,下方 示例 代码可直接复制运行。
- 使用组件需要关闭下拉刷新事件,page.json -> "enablePullDownRefresh": false
使用说明
- APP端需要配置manifest.json -> App模块配置 -> 勾选VideoPlay(视频播放)。
- 运行到微信小程序videoSrc要为http/https开头。
安装方式
本组件符合easycom规范,
HBuilderX 2.5.5
起,只需将本组件导入项目,在页面template
中即可直接使用,无需在页面中import
和注册components
。
APP端特别说明
APP 端特别说明
APP 端特别说明
APP 端特别说明
代码演示
以下代码 可自行参考改动,APP、H5、小程序 均可直接复制 粘贴运行,APP端请使用 ·nvue· 以下代码无需特殊改动便可运行
注意:该组件 依赖于 ml-player
播放器组件,请自行下载 ml-player 播放器
<template>
<ml-swiper
:videoList="realList"
:width="width"
:height="height"
@loadMore="loadMore"
@change="onchange"
@play="play"
@pause="pause"
@ended="ended"
@error="error"
@waiting="waiting"
@videoClick="videoClick"
@doubleClick="doubleClick"
@maskClick="maskClick">
<!-- 使用 right 插槽,定义右侧 用户头像、喜欢、收藏、设置等 -->
<template v-slot:right="{ video, index }">
<view class="uniui-right">
<!-- 用户头像 -->
<image v-if="video" class="userAvatar" :src="video?.poster"></image>
<!-- 喜欢数量 -->
<uni-icons type="heart-filled" :color="index % 2 == 0 ? '#ff0000' : '#fff'" size="25">
</uni-icons>
<text class="iconTitle">{{ Math.ceil(Math.random() * 9999) }}</text>
<!-- 收藏数量 -->
<uni-icons type="star-filled" :color="index % 2 != 0 ?'#ffaa00':'#fff'" size="25">
</uni-icons>
<text class="iconTitle">{{ Math.ceil(Math.random() * 9999) }}</text>
<!-- 视频设置 -->
<uni-icons type="gear-filled" size="25" color="#fff"></uni-icons>
<text class="iconTitle">设置</text>
</view>
</template>
<!-- 使用 bottom 插槽,定义底部 视频标题等 -->
<template v-slot:bottom="{ video, index }">
<!-- 视频标题 -->
<text class="videoTitle" v-if="video">
{{ video?.title }}
</text>
</template>
</ml-swiper>
</template>
<script setup >
import { ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
const win = uni.getSystemInfoSync();
const width = win.windowWidth; // 视频组件宽度,可传可不传
const height = win.windowHeight; // 视频组件高度,可传可不传
const realList = ref([]); // 【必须传入,{url:"必填",title:"非必填,建议传入",poster:"预览图,非必填,建议传入",...:"可传入其他自定义参数,插槽中可以拿到"}】
const current = ref(0); // 当前播放的视频的索引
let context = null; // 视频上下文对象,用于操作视频组件(暂停、播放、倍速、静音、弹幕等)
const counter = ref(0);
// 视频滑动事件,根据需要,可写可不写,非必须
const onchange = (index) => {
console.log("onchange-当前索引:", index);
current.value = index;
};
// 是否加载更多,根据需要,可写可不写,非必须
const loadMore = (index, size) => {
console.log("加载更所视频:", index + " / " + size);
// 请求 5 次 后不在请求
if (counter.value > 5) return;
// 模拟从后台请求数据,并将请求到的数据追加到列表中
getList().forEach((item) => {
item.title = realList.value.length + "," + item.title + item.title + item.title;
realList.value.push(item);
});
counter.value = counter.value + 1;
};
// 视频开始播放,根据需要,可写可不写,非必须
const play = (context) => {
context = context;
console.log("视频开始播放");
uni.showToast({ title: "视频开始播放", icon: "none", mask: false });
};
// 视频暂停播放,根据需要,可写可不写,非必须
const pause = (context) => {
context = context;
console.log("视频暂停播放");
uni.showToast({ title: "视频暂停播放", icon: "none", mask: false });
};
// 视频播放结束,根据需要,可写可不写,非必须
const ended = (context) => {
context = context;
console.log("视频播放结束");
uni.showToast({ title: "视频播放结束", icon: "none", mask: false });
};
// 视频播放出错,根据需要,可写可不写,非必须
const error = (context, event) => {
context = context;
console.error("视频播放出错:", event);
uni.showToast({ title: "视频播放出错", icon: "none", mask: false });
};
// 视频出现缓冲,根据需要,可写可不写,非必须
const waiting = (context) => {
context = context;
console.error("视频出现缓冲");
uni.showToast({ title: "视频出现缓冲", icon: "none", mask: false });
};
// 视频点击事件,根据需要,可写可不写,非必须
const videoClick = (context, video) => {
context = context;
console.log("点击了视频:", video);
uni.showToast({ title: "点击了视频", icon: "none", mask: false });
};
// 视频双击事件,根据需要,可写可不写,非必须
const doubleClick = (context, video) => {
context = context;
console.log("双击了视频:", video);
uni.showToast({ title: "双击了视频", icon: "none", mask: false });
};
// 蒙层点击事件,根据需要,可写可不写,非必须
const maskClick = (index, video) => {
context = context;
console.log("点击了蒙层:", index, video);
uni.showToast({ title: "点击了蒙层", icon: "none", mask: false });
};
/**
* 短视频列表
*/
const getList = () => {
return [{
videoId: realList.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: realList.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: realList.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: realList.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: "女"
}
}
];
}
onLoad(() => {
// 组件加载时,模拟请求后台获取到数据,并向列表中追加数据
getList().forEach((item) => {
item.title = realList.value.length + "," + item.title + item.title + item.title;
realList.value.push(item);
});
});
</script>
<style scoped lang="scss">
.uniui-right {
/* #ifndef APP-NVUE */
display: grid;
text-align: center;
margin: 0 auto;
/* #endif */
justify-content: center;
}
.videoTitle {
padding: 5px;
color: #fff;
font-size: 13px;
lines: 3;
white-space: normal;
}
.userAvatar {
width: 35px;
height: 35px;
border-radius: 100px;
margin-bottom: 10px;
border: 1rpx solid #fff;
background-color: #fafafa;
}
.iconTitle {
font-size: 12px;
color: #fff;
text-align: center;
padding-bottom: 5px;
}
</style>
props
属性名 | 类型 | 默认值 | 可选值 | 说明 | 必填 |
---|---|---|---|---|---|
width | Number | - | - | 播放器宽度,默认设备同宽 | 否 |
height | Number | - | - | 播放器高度,默认设备同高 | 否 |
rightStyle | Object, String | - | - | 自定义右侧样式(用户头像、点赞、收藏...) | 否 |
bottomStyle | Object, String | - | - | 自定义底部样式(视频标题、用户昵称...) | 否 |
videoList | Array | - | - | 视频资源列表,option 详情见下文 |
是 |
count | Number | 2 | - | 临界值,当视频剩余 多少时 触发加载更多函数(loadMore ) |
否 |
showPlay | Boolean | true | false | 应用从后台显示时 是否播放视频,同 uniapp onShow | 否 |
hidePause | Boolean | true | false | 应用从前台隐藏时 是否暂停视频,同 uniapp onHide | 否 |
videoList详情配置
属性名 | 类型 | 默认值 | 可选值 | 说明 | 必填 |
---|---|---|---|---|---|
url | String | - | - | 视频资源的url链接地址 | 是 |
title | String | - | - | 视频资源的标题 | 否 |
poster | String | - | - | 视频资源的预览图 | 否 |
- | - | - | - | - | - |
-- 其他可根据需要自行定义 -- | - | - | - | - | - |
likes喜欢数、star收藏数... | Any | - | - | 可自行添加其他属性,插槽中可使用 | - |
userinfo |
Object | {} | - | 自定义userinfo 属性,插槽中可使用 |
- |
comment |
Array | [] | - | 自定义comment 评论列表数据,插槽中可使用 |
- |
事件 Events
事件名 | 返回参数 | 说明 |
---|---|---|
@change | index:当前索引 | 视频滑动事件,返回当前视频的索引 |
@play | context:视频上下文对象,可以操作 video 组件 | 视频开始播放,返回 context 视频上下文对象 |
@pause | context:视频上下文对象,可以操作 video 组件 | 视频暂停播放,返回 context 视频上下文对象 |
@ended | context:视频上下文对象,可以操作 video 组件 | 视频播放结束,返回 context 视频上下文对象 |
@error | context:视频上下文对象,event:错误信息 | 视频播放出错,返回 context ,event:错误信息 |
@waiting | context:视频上下文对象,可以操作 video 组件 | 视频出现缓冲,返回 context 视频上下文对象 |
@videoClick | context:视频上下文对象,video:视频数据 | 视频点击事件,返回 context,video:视频数据 |
@doubleClick | context:视频上下文对象,video:视频数据 | 视频双击事件,返回 context,video:视频数据 |
@maskClick | index:当前视频的索引,video:视频数据 | 蒙层点击事件,返回 index,video:视频数据 |
@loadMore | index,当前视频的索引,size:视频列表的长度 | 加载更多数据,返回 index,size:视频列表的长度 |
注意事项
- APP需要按照文档进行配置。
- APP端,需要使用
.nvue
文件。 - APP端需要配置manifest.json -> App模块配置 -> 勾选VideoPlay(视频播放)。
使用组件需要关闭下拉刷新事件,page.json -> "enablePullDownRefresh": false