更新记录
1.2.6(2025-02-08)
- 修复
ml-swiper-v3
在app端的已知BUG
1.2.5(2025-01-07)
- 新增
ml-swiper-app
app特有组件,修复ml-swiper-v3
已知BUG(文档末尾提供示例代码)
1.2.4(2024-12-19)
- 调整组件中的钩子函数mounted->created
平台兼容性
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
特别说明
特别说明:
收费代码有版权问题,且发布后的插件会被自动加密,如非购买本插件出现的各种问题,均与此有关。
如正常购买了插件,出现了运行报错,或者案例代码运行报错,均可入群与作者联系,作者将提供帮助,并解决问题。
该组件将持续维护,有任何问题均可留言。
非购买出现的问题,非组件本身问题 作者将不予处理和解决,切勿恶意差评,创作不易 还请理解。
购买普通版权后,想要源码的可插件市场直接购买源码,如需要继续扩展 可以直接联系作者 索要底层源代码 以供扩展使用。
使用案例时特别注意,案例中的函数名会被转义为空,报错时 请先检查是否存在函数名为空的,为空时补全后重新运行即可。
推荐使用使用 ml-swiper-v3 组件,底层已经封装完成,无需考虑数据量的问题,开箱即用。
功能概览
支持视频双击点赞功能,单击暂停播放和播放功能
;支持配置 是否自动切换下一个视频,当前视频播放完成后 会自动切换到下一个视频
;支持 m3u8 流媒体视频资源,可以直接使用,无需特殊配置
;提供可自定义进度条插槽,可以实现自定义进度条功能,不想自定义可以使用默认进度条功能
;支持微信小程序、H5、和 APP
;提供了多种插槽,可以完全实现自定义各种组件
;进度条拖动功能
;视频、图片展示功能
;以上只是简单几个小功能,更多具体功能请详细阅读文档
案例报错说明
缺少函数名
网页示例缺少函数名,可以直接使用readme.md
中的示例代码
// @ondblclick
function (e) { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释 被转义掉的函数名为:
console.log(e);
}
使用案例时特别注意,案例中的函数名会被转义为空,报错时 请先检查是否存在函数名为空的,为空时补全后重新运行即可。
试用时报错
试用时报错:Failed to mount component: template or render function not defined. found in......
如果试用时组件运行报错
[Vue warn]: Failed to mount component: template or render function not defined. found in ......
请注意,原因是试用组件 被加密了,导致无法被解析到,正常购买不会有问题,如有其他问题可在群里提出,作者看到后会帮助解决处理。
初始值为空
初始值为空:[system] TypeError: Cannot read properties of undefined (reading 'includes')at ......
如果购买了插件,运行时报错:
[system] TypeError: Cannot read properties of undefined (reading 'includes')at ......
,原因是datas[0]?.includes
初始值为空,导致的报错,请修改示例代码
<!-- 点赞:修改前 -->
<uni-icons type="heart-filled" size="35" :color="datas[0]?.includes(item.videoId) ? '#ff0004' : '#fff'" @tap="iconClick(0, item.videoId)" />
<!-- 修改后 -->
<uni-icons type="heart-filled" size="35" color="#fff" />
<!-- 收藏:修改前 -->
<uni-icons type="star-filled" size="35" :color="datas[1]?.includes(item.videoId) ? '#ff0' : '#fff'" @tap="iconClick(1, item.videoId)" />
<!-- 修改后 -->
<uni-icons type="star-filled" size="35" color="#fff" />
组件支持情况
ml-swiper-v3
版本:支持 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页面,并且提供了 很多组件方法 便于自定义实现更多的业务逻辑;
ml-swiper-x:该 插件是 uni-app-x
版,性能远远高于 uni-app 流畅度和体验度 大大提升,让短视频刷起来更加流程丝滑(注:这得益于uni-app-x
打包成原生代码,大大提升了性能)
新特性概览
进度条拖动
视频图片组合
自动切换
真实运行示例
APP-雷电模拟器

APP端代码示例

手机端-微信小程序


手机端-QQ浏览器


小程序、H5网页端代码示例

拖动进度条"
<ml-swiper-v3 v-if="lists && lists.length > 0" :list="lists" :customProgress="false" >
<!-- ...... 省略重复代码 -->
</ml-swiper-v3>

自动切换下一个:autoChange="true"
<ml-swiper-v3 v-if="lists && lists.length > 0" :list="lists" :autoChange="true" >
<!-- ...... 省略重复代码 -->
</ml-swiper-v3>

视频图片结合:imgList
<ml-swiper-v3 v-if="lists && lists.length > 0" :list="lists" :autoChange="true" >
<!-- ...... 省略重复代码 -->
</ml-swiper-v3>
<script setup>
import { ref } from 'vue';
const lists = ref([
{
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 + 1,
title: `图片列表测试。`,
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: "男"
},
imgList: [
'http://gips2.baidu.com/it/u=195724436,3554684702&fm=3028',
'http://gips3.baidu.com/it/u=3886271102,3123389489&fm=3028',
'http://gips0.baidu.com/it/u=3602773692,1512483864&fm=3028',
'http://gips3.baidu.com/it/u=119870705,2790914505&fm=3028',
'http://gips0.baidu.com/it/u=2298867753,3464105574&fm=3028',
'http://gips2.baidu.com/it/u=3944689179,983354166&fm=3028'
]
}
// ...... 省略重复代码
]);
</script>

组件参数props
属性名 | 类型 | 默认值 | 说明 | 必须 |
---|---|---|---|---|
list |
Array | [] | 视频数据,默认空数组,参数详情下见options 介绍,数据资源条数最少不得少于 3 条 |
是 |
width |
String | 100% | 组件宽度,默认与设备同宽 | 否 |
height |
String | 100% | 组件高度,默认与设备同高 | 否 |
criticalVal |
Number | 2 | 临界值,当 list.length - currentIndex >= criticalVal 时触发加载更多事件 |
否 |
progress |
Boolean | true | 是否显示进度条,默认显示进度条 | 否 |
duration |
Boolean | true | 是否显示播放时间,默认显示播放时长 | 否 |
customProgress |
Boolean | false | 自定义进度条 | 否 |
autoChange |
Boolean | false | 是否自动切换下一个视频 | 否 |
slideDuration |
Number | 500 | 滑动动画时长,默认 500 与uniapp官方保持一致 | 否 |
showFullscreen |
String | none | 可选值:always | auto | none ,APP端设置 auto 时和 always 一样,将一直显示,H5端设置 auto 时,将动态计算是否需要显示 全屏观看 按钮 |
否 |
customFullscreen |
Boolean | false | 是否使用自定义全屏观看按钮插槽,需要将 showFullscreen置为"always",并且customFullscreen为 true 可以配合 插槽 "fullscreen" 一起使用 | 否 |
startIndex |
Number | 0 | 开始索引,从第 startIndex 个资源开始,指定开始索引,并且startIndex < list.length |
否 |
initTipText |
String | 初始化中... | 初始化提示文本,默认 初始化中... | 否 |
customInitTip |
Boolean | false | 自定义初始化文本插槽 | 否 |
showPlayBtn |
Boolean | false | 是否显示 播放按钮 | 否 |
slidingDistance |
Number | 50 | 滑动距离 临界值,当无法触发swiper时将使用此值 | 否 |
视频数据详情options
【数据资源条数最少不得少于 3
条】
属性名 | 类型 | 默认值 | 说明 | 必须 |
---|---|---|---|---|
url |
String | "" | 视频资源地址 | 否 |
title |
String | "" | 视频标题 | 否 |
poster |
String | "" | 视频封面,支持JPG、PNG等常见图片文件 | 否 |
imgList |
Array<String> |
[] | 用于展示图片资源 | 否 |
----- | ----- | 其他属性可根据需要自定义 | ----- | ----- |
比如: | userInfo = {} |
用户相关数据 | ||
比如: | commentList = [] |
评论数据列表 | ||
比如: | date |
视频发布时间 | ||
比如: | location |
IP属地信息 | ||
。。。 | 。。。 | 。。。 | 。。。 | 。。。 |
组件事件Events
事件 | 参数 | 解释 | 说明 |
---|---|---|---|
onchange (event) 滑动事件 |
event = {index, context, video} | index:当前视频的索引 context:video的上下文对象 video:正在播放的视频数据 |
当视频上下滑动时,触发 onchange 事件 |
onplay (event) 播放事件 |
event = {index, context, video, playing} | index:当前视频的索引 context:video的上下文对象 video:正在播放的视频数据 playing:当前播放状态 |
当前视频播放时,触发 onplay 事件 |
onpause (event) 暂停事件 |
event = {index, context, video, playing} | index:当前视频的索引 context:video的上下文对象 video:正在播放的视频数据 playing:当前播放状态 |
当前视频暂停时,触发 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 时触发加载更多事件 |
fullscreenclick(event) 全屏时的点击事件 |
event = { screenX, screenY, screenWidth, screenHeight } | 全屏时的点击坐标和全屏时的屏幕宽度和高度 | 仅全屏时点击才会触发当前事件 |
fullscreenchange(event) 当视频进入和退出全屏事件 |
event = {fullScreen, direction} | direction取为 vertical 或 horizontal | 当进入全屏和退出全屏时触发fullscreenchange 事件,需要判断当前是否全屏时,可以使用此事件 |
longTap(event) 视频组件长按事件 |
event = {index, context, video, playing} | index:当前视频的索引 context:video的上下文对象 video:当前视频的数据信息 playing:是否正在播放 |
当长按视频组件时会触发longTap(event) 事件 |
组件插槽slot
插槽name | 插槽参数 | 使用场景 |
---|---|---|
default | item :当前视频资源数据index :当前视频索引 |
视频上方自定义播放按钮或者其他功能 |
right | item :当前视频资源数据index :当前视频索引 |
可以实现抖音右侧工具条:点赞、收藏、评论、设置等 |
bottom | item :当前视频资源数据index :当前视频索引 |
可以实现抖音作者名称,视频标题等 |
progress | current :当前时间(s)duration :总时长(s)percentage :进度比 |
可以配合ontimeupdate 拿到视频时长和当前时长 实现自定义进度条,需要progress 和 customProgress 都为 true ; |
fullscreen | item :当前视频资源数据index :当前视频索引 |
自定义全屏按钮插槽 |
initText | - | 自定义初始化文本插槽 |
插槽示例代码
default
默认插槽
<template v-slot:default="{ index }">
<view class="body">
<text class="text">{{ index + 1 }} / {{ lists.length }}</text>
</view>
</template>
right
右侧工具条
<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="#fff" />
<text class="icon-val">666</text>
</view>
<!-- 评论 -->
<view class="icon">
<uni-icons type="chat-filled" size="35" color="#fff" />
<text class="icon-val">668</text>
</view>
<!-- 收藏 -->
<view class="icon">
<uni-icons type="star-filled" size="35" color="#fff" />
<text class="icon-val">888</text>
</view>
<!-- 转发 -->
<view class="icon">
<uni-icons type="redo-filled" size="35" color="#fff" />
<text class="icon-val">999</text>
</view>
</view>
</template>
bottom
底部视频标题
<template v-slot:bottom="{ item }">
<view class="bottom">
<text class="title">{{ item?.title }}</text>
</view>
</template>
progress
底部自定义进度条,需要customProgress
和 progress
同时设置为 true
<template v-slot:progress="{ current, duration, percentage }">
<view v-if="percentage" class="progress-box">
<text>{{ current }}</text>
<progress class="progress" :percent="percentage" font-size="13"
stroke-width="2" activeColor="#3ea5ff" backgroundColor="#EBEBEB"
active active-mode="forwards" />
<text>{{ duration }}</text>
</view>
</template>
组件方法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-v3 ref="mlSwiperRef"></ml-swiper-v3>
<!-- ...省略... -->
<script setup>
import { ref } from 'vue';
const mlSwiperRef = ref(null);
// ...省略...
/**
* @fullScreen 全屏
*/
function fullScreen() { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
mlSwiperRef.value?.fullScreen();
// context?.requestFullScreen();
}
/**
* @exitFullScreen 退出全屏
*/
function exitFullScreen() { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
mlSwiperRef.value?.exitFullScreen();
// context?.exitFullScreen();
}
/**
* @setRate 倍速
* @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);
}
/**
* @setSeek 跳转到指定位置播放
* @param {Number} val 单位秒
*/
function setSeek(val) { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
mlSwiperRef.value?.setSeek(val);
// context?.seek(val);
}
/// ...省略...
</script>
vue3 版本示例代码
组件示例代码
微信小程序 和 H5 网页端示例代码
(注:H5端 需要安装 hls,详见下文: H5端使用说明
)
# H5 端 安装插件
npm install hls.js
示例代码中用到了 uni-icons
请自行安装
index.vue
<template>
<!-- 这里使用 v-if 是为了防止 请求后台时还没返回数据 导致组件初始化时没有视频资源 而出现错误 -->
<ml-swiper-v3 v-if="lists && lists.length > 0" :list="lists" @loadmore="loadmore" @ondblclick="" @onplay="onplay">
<!-- 自定义内容,这里设置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>
<script setup>
import { onMounted, reactive, ref } from 'vue';
import { onHide } from "@dcloudio/uni-app";
const lists = ref([]);
const type = ref("v3"); // 当前 ml-swiper版本(当前版本是 ml-swiper-v3)
const datas = reactive({0:[],1:[]});
let count = 0, context = null;
// @onplay 视频播放
function onplay(event){
context = event?.context;
console.log("onplay:", event?.playing);
}
// @avatarClick 点击头像
function avatarClick() { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
uni.showToast({ title: "点击头像", icon: "none", duration: 1000 });
}
// @comment 查看评论
function comment(_id) { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
uni.showToast({ title: "查看评论", icon: "none", duration: 1000 });
}
// @forward 转发
function forward(_id) { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
uni.showToast({ title: "转发", icon: "none", duration: 1000 });
}
// @iconClick 图标点击事件
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);
}
}
// @ondblclick 屏幕双击事件
function ({ video }) { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
console.log(video?.videoId);
iconClick(0, video?.videoId);
}
// @loadmore 加载更多
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);
});
}
}
// 页面隐藏时,暂停播放
onHide(() => {
if (context) { context?.pause(); }
});
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: `御姐美女抖音作品,来个自拍视频把,好美啊。`,
uploadTime: "2023-10-02 09:41",
ipLocation: "贵州",
author: {
authorId: 102,
avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
nickName: "御姐呀",
genderName: "女"
},
imgList: [
'http://gips2.baidu.com/it/u=195724436,3554684702&fm=3028',
'http://gips3.baidu.com/it/u=3886271102,3123389489&fm=3028',
'http://gips0.baidu.com/it/u=3602773692,1512483864&fm=3028',
'http://gips3.baidu.com/it/u=119870705,2790914505&fm=3028',
'http://gips0.baidu.com/it/u=2298867753,3464105574&fm=3028',
'http://gips2.baidu.com/it/u=3944689179,983354166&fm=3028'
]
},
{
videoId: lists.value.length + 3,
title: `抖音主播可爱妹子新学的舞蹈,超可爱的美女主播。`,
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: `多个美女带着遮阳帽出去散步自拍视频,好好看。`,
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: "女"
},
imgList: [
'http://gips2.baidu.com/it/u=195724436,3554684702&fm=3028',
'http://gips3.baidu.com/it/u=3886271102,3123389489&fm=3028',
'http://gips0.baidu.com/it/u=3602773692,1512483864&fm=3028',
'http://gips3.baidu.com/it/u=119870705,2790914505&fm=3028',
'http://gips0.baidu.com/it/u=2298867753,3464105574&fm=3028',
'http://gips2.baidu.com/it/u=3944689179,983354166&fm=3028'
]
}
];
}
</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%;
}
.progress {
/* #ifndef APP-NVUE */
width: 80%;
/* #endif */
height: 2px;
}
.progress-box {
/* #ifndef APP-NVUE */
width: 100%;
display: flex;
color: #fff;
/* #endif */
align-items: center;
justify-content: space-around;
}
.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端提供了 ml-swiper-v3
和 ml-swiper-app
两个组件,请根据实际情况进行选择
示例代码中用到了 uni-icons
请自行安装
index.nvue
下面是 ml-swiper-v3
示例代码
<template>
<!-- 这里使用 v-if 是为了防止 请求后台时还没返回数据 导致组件初始化时没有视频资源 而出现错误 -->
<ml-swiper-v3 v-if="lists && lists.length > 0" :list="lists" @loadmore="loadmore" @ondblclick="" @onplay="onplay">
<!-- 右侧 -->
<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>
<script setup >
import { onMounted, reactive, ref } from 'vue';
import { onHide } from "@dcloudio/uni-app";
const lists = ref([]);
const type = ref("v3");
const datas = reactive({0:[],1:[]});
let count = 0, context = null;
// @onplay 视频播放
function onplay(event){
context = event?.context;
console.log("onplay:", event?.playing);
}
// @avatarClick 点击头像
function avatarClick() { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
uni.showToast({ title: "点击头像", icon: "none", duration: 1000 });
}
// @comment 查看评论
function comment(_id) { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
uni.showToast({ title: "查看评论", icon: "none", duration: 1000 });
}
// @forward 转发
function forward(_id) { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
uni.showToast({ title: "转发", icon: "none", duration: 1000 });
}
// @iconClick 图标点击事件
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);
}
}
// @ondblclick 双击事件
function ({ video }) { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
console.log(video?.videoId);
iconClick(0, video?.videoId);
}
// @loadmore 加载更多
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);
});
}
}
// 页面隐藏时 暂停播放
onHide(() => {
if (context) { context?.pause(); }
});
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: `御姐美女抖音作品,来个自拍视频把,好美啊。`,
uploadTime: "2023-10-02 09:41",
ipLocation: "贵州",
author: {
authorId: 102,
avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
nickName: "御姐呀",
genderName: "女"
},
imgList: [
'http://gips2.baidu.com/it/u=195724436,3554684702&fm=3028',
'http://gips3.baidu.com/it/u=3886271102,3123389489&fm=3028',
'http://gips0.baidu.com/it/u=3602773692,1512483864&fm=3028',
'http://gips3.baidu.com/it/u=119870705,2790914505&fm=3028',
'http://gips0.baidu.com/it/u=2298867753,3464105574&fm=3028',
'http://gips2.baidu.com/it/u=3944689179,983354166&fm=3028'
]
},
{
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: `多个美女带着遮阳帽出去散步自拍视频,好好看。`,
uploadTime: "2023-07-02 14:41",
ipLocation: "山西",
author: {
authorId: 104,
avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
nickName: "蓝姬",
genderName: "女"
},
imgList: [
'http://gips2.baidu.com/it/u=195724436,3554684702&fm=3028',
'http://gips3.baidu.com/it/u=3886271102,3123389489&fm=3028',
'http://gips0.baidu.com/it/u=3602773692,1512483864&fm=3028',
'http://gips3.baidu.com/it/u=119870705,2790914505&fm=3028',
'http://gips0.baidu.com/it/u=2298867753,3464105574&fm=3028',
'http://gips2.baidu.com/it/u=3944689179,983354166&fm=3028'
]
}
];
}
</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;
}
.progress {
/* #ifndef APP-NVUE */
width: 80%;
/* #endif */
height: 2px;
}
.progress-box {
/* #ifndef APP-NVUE */
width: 100%;
display: flex;
color: #fff;
/* #endif */
align-items: center;
justify-content: space-around;
}
.bottom {
margin: 10px;
}
.title {
color: #fff;
font-size: 13px;
}
</style>
下面是 ml-swiper-app
示例代码
<template>
<view :style="`width:${width}px;height:${height}px;overflow:hidden;`">
<ml-swiper-app v-if="list.length > 2" :list="list" :width="width" :height="height" :custom="custom"
:progress="progress" :criticalVal="criticalVal" :autoplay="autoplay" :startIndex="startIndex"
:autoChange="autoChange" @change="onchange" @toupper="toupper" @tolower="tolower" @loadmore="loadmore"
@ondblclick="dblclick" @onplay="onplay" @onpause="onpause" @onended="" @onerror="onerror"
@onwaiting="" @ontimeupdate="" @fullscreenchange="fullscreenchange"
@fullscreenclick="fullscreenclick">
<!-- custom="true":自定义 video 组件 -->
<template v-if="custom" v-slot:default="{item}">
<video :src="item.url" :title="item.title" :poster="item.poster" :autoplay="false" :controls="false"
:loop="true" :show-center-play-btn='true' play-btn-position="center" :show-loading="false"
:enable-progress-gesture="false" object-fit="contain" :style="`width:${width}px;height:${height}px;`"></video>
</template>
<!-- custom="false":使用默认 video 组件 -->
<!-- 默认插槽:default = {item: "当前资源数据", index: "当前资源索引"} -->
<template v-slot:default="{ index }">
<view> <text style="color: #fff;">{{ index + 1 }} / {{ list.length }}</text> </view>
</template>
<!-- 右侧插槽:right = {item: "当前资源数据", index: "当前资源索引"} -->
<template v-slot:right="{ item }">
<view class="right">
<image class="userAvatar" :src="item?.author?.avatar"></image>
<!-- 喜欢 -->
<view class="icon">
<uni-icons type="heart-filled" size="35" color="#ff0004" />
<text class="icon-val">666</text>
</view>
<!-- 评论 -->
<view class="icon">
<uni-icons type="chat-filled" size="35" color="#fff" />
<text class="icon-val">668</text>
</view>
<!-- 收藏 -->
<view class="icon">
<uni-icons type="star-filled" size="35" color="#ff0" />
<text class="icon-val">888</text>
</view>
<!-- 转发 -->
<view class="icon">
<uni-icons type="redo-filled" size="35" color="#fff" />
<text class="icon-val">999</text>
</view>
</view>
</template>
<!-- 底部插槽:bottom = {item: "当前资源数据", index: "当前资源索引"} -->
<template v-slot:bottom="{ item }">
<view :style="`width:${width}px;`">
<text style="color: #fff;">{{ item.title }}</text>
</view>
</template>
<!-- progress="false":不使用默认进度条,自定义进度条,需要配合 @ontimeupdate 事件获取当前播放时间 -->
</ml-swiper-app>
</view>
</template>
<script>
import mlSwiperApp from '../../uni_modules/ml-swiper-v3/components/ml-swiper-v3/ml-swiper-app.vue';
export default {
components: {
mlSwiperApp
},
data() {
return {
list: [], // 视频数据列表:{url:"视频资源地址", poster:"预览图,尽量给出,否则上下滑动时会黑屏", title:"视频标题"}
custom: false, // 是否自定义 video 组件, 默认false
progress: true, // 是否显示进度条,默认 true
autoChange: true, // 是否自动播放下一个
criticalVal: 2, // 临界值,默认 2
autoplay: true, // 初次加载时视频是否自动播放
startIndex: 0, // 开始索引,从第 n 个资源开始,默认0
width: uni.getSystemInfoSync().windowWidth, // 组件宽度
height: uni.getSystemInfoSync().windowHeight // 组件高低
}
},
created() {
this.list = this.getList();
},
mounted() {},
onHide() { // 如果需要离开页面暂停播放,尽量提供 onHide
this.pause();
},
onUnload() {
this.pause();
this.list = [];
this.context = null;
},
methods: {
onchange(e) {
console.log(" === onchange 滑动 ===> ", e);
console.log("\r\n");
},
toupper(e) {
console.log(" === toupper 到顶了 ===> ", e);
console.log("\r\n");
uni.showToast({
title: "到顶了",
icon: 'none'
});
},
tolower(e) {
console.log(" === tolower 到底了 ===> ", e);
console.log("\r\n");
uni.showToast({
title: "到底了",
icon: 'none'
});
},
loadmore(e) {
console.log(" === loadmore 加载更多 ===> ", e);
console.log("\r\n");
uni.showToast({
title: "加载更多",
icon: 'none'
});
this.list = [...this.list, ...this.getList()];
},
dblclick(e) {
console.log(" === dblclick 双击 ===> ", e);
console.log("\r\n");
uni.showToast({
title: "双击",
icon: 'none'
});
},
onplay(e) {
this.context = e.context;
console.log(" === onplay 开始播放 ===> ", e);
console.log("\r\n");
},
onpause(e) {
console.log(" === onpause 暂停播放 ===> ", e);
console.log("\r\n");
},
(e) {
console.log(" === 播放结束 ===> ", e);
console.log("\r\n");
},
onerror(e) {
console.error(e);
},
(e) {
console.log(" === 出现缓冲 ===> ", e);
console.log("\r\n");
},
(e) {
console.log(" === 更新时长 ===> ", e);
},
fullscreenchange(e) {
console.log(" === 全屏变更 ===> ", e);
console.log("\r\n");
},
fullscreenclick(e) {
console.log(" === 全屏点击 ===> ", e);
console.log("\r\n");
},
pause() {
if (this.context?.pause) {
this.context.pause();
}
},
play() {
if (this.context?.play) {
this.context.play();
}
},
getList() {
return [{
videoId: Math.ceil(Math.random() * 689),
likes: Math.ceil(Math.random() * 689),
stars: Math.ceil(Math.random() * 689),
isLike: 0,
title: `抖音美女主播,JK超短裙学生妆美女跳舞展示,爱了爱了。`,
poster: "http://gips2.baidu.com/it/u=195724436,3554684702&fm=3028",
url: "https://alimov2.a.yximgs.com/upic/2020/07/02/14/BMjAyMDA3MDIxNDUyMDlfOTExMjIyMjRfMzE1OTEwNjAxNTRfMV8z_b_Bf3005d42ce9c01c0687147428c28d7e6.mp4",
uploadTime: "2023-11-08 19:41",
ipLocation: "上海",
author: {
authorId: 101,
avatar: "http://gips2.baidu.com/it/u=195724436,3554684702&fm=3028",
nickName: "陌路",
genderName: "男"
}
},
{
videoId: Math.ceil(Math.random() * 689),
likes: Math.ceil(Math.random() * 689),
stars: Math.ceil(Math.random() * 689),
isLike: 0,
title: `图片列表测试。`,
poster: "http://gips3.baidu.com/it/u=3886271102,3123389489&fm=3028",
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: "http://gips3.baidu.com/it/u=3886271102,3123389489&fm=3028",
nickName: "陌路",
genderName: "男"
},
imgList: [
'http://gips2.baidu.com/it/u=195724436,3554684702&fm=3028',
'http://gips3.baidu.com/it/u=3886271102,3123389489&fm=3028',
'http://gips0.baidu.com/it/u=3602773692,1512483864&fm=3028',
'http://gips3.baidu.com/it/u=119870705,2790914505&fm=3028',
'http://gips0.baidu.com/it/u=2298867753,3464105574&fm=3028',
'http://gips2.baidu.com/it/u=3944689179,983354166&fm=3028'
]
},
{
videoId: Math.ceil(Math.random() * 689),
likes: Math.ceil(Math.random() * 689),
stars: Math.ceil(Math.random() * 689),
isLike: 0,
title: `御姐美女抖音作品,来个自拍视频把,好美啊。`,
poster: "http://gips0.baidu.com/it/u=3602773692,1512483864&fm=3028",
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: "http://gips0.baidu.com/it/u=3602773692,1512483864&fm=3028",
nickName: "御姐呀",
genderName: "女"
}
},
{
videoId: Math.ceil(Math.random() * 689),
likes: Math.ceil(Math.random() * 689),
stars: Math.ceil(Math.random() * 689),
isLike: 0,
title: `图片列表测试。`,
poster: "http://gips3.baidu.com/it/u=119870705,2790914505&fm=3028",
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: "http://gips3.baidu.com/it/u=119870705,2790914505&fm=3028",
nickName: "陌路",
genderName: "男"
},
imgList: [
'http://gips2.baidu.com/it/u=195724436,3554684702&fm=3028',
'http://gips3.baidu.com/it/u=3886271102,3123389489&fm=3028',
'http://gips0.baidu.com/it/u=3602773692,1512483864&fm=3028',
'http://gips3.baidu.com/it/u=119870705,2790914505&fm=3028',
'http://gips0.baidu.com/it/u=2298867753,3464105574&fm=3028',
'http://gips2.baidu.com/it/u=3944689179,983354166&fm=3028'
]
},
{
videoId: Math.ceil(Math.random() * 689),
likes: Math.ceil(Math.random() * 689),
stars: Math.ceil(Math.random() * 689),
isLike: 0,
title: `抖音主播可爱妹子新学的舞蹈,超可爱的美女主播。`,
poster: "http://gips0.baidu.com/it/u=2298867753,3464105574&fm=3028",
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: "http://gips0.baidu.com/it/u=2298867753,3464105574&fm=3028",
nickName: "野花猫",
genderName: "女"
}
},
{
videoId: Math.ceil(Math.random() * 689),
likes: Math.ceil(Math.random() * 689),
stars: Math.ceil(Math.random() * 689),
isLike: 0,
title: `多个美女带着遮阳帽出去散步自拍视频,好好看。`,
poster: "http://gips2.baidu.com/it/u=3944689179,983354166&fm=3028",
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: "http://gips2.baidu.com/it/u=3944689179,983354166&fm=3028",
nickName: "蓝姬",
genderName: "女"
}
}
];
}
}
}
</script>
<style scoped lang="scss">
.text {
font-size: 50px;
color: #ff5918;
}
.right {
width: 50px;
padding: 10px 0;
margin: 0;
}
.icon {
display: flex;
flex-wrap: wrap;
flex-direction: column;
justify-content: center;
}
.icon-val {
color: #fff;
font-size: 13px;
text-align: center;
}
.userAvatar {
width: 40px;
height: 40px;
border-radius: 100%;
}
</style>
vue2 版本示例代码
vue代码示例
示例代码中用到了 uni-icons
请自行安装
<template>
<view class="wh-full">
<!-- 这里使用 v-if 是为了防止 请求后台时还没返回数据 导致组件初始化时没有视频资源 而出现错误 -->
<ml-swiper-v3 v-if="lists && lists.length > 0" :list="lists" @loadmore="loadmore" @ondblclick="" @onplay="onplay">
<!-- 自定义内容,这里设置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>
</view>
</template>
<script>
export default {
data() {
return {
lists: [], // 资源数据
datas: {0:[],1:[]}, // 点赞、收藏等数据
count: 0, // 计数器
context: null,
type: "v3" // 使用的组件版本,v2:ml-swiper-v2、v3:ml-swiper-v3
}
},
onHide() {
if (this.context) { this.context?.pause(); }
},
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: {
// @onplay
onplay(event) {
this.context = event?.context;
},
// @avatarClick
avatarClick() { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
uni.showToast({ title: "点击头像", icon: "none", duration: 1000 });
},
// @comment
comment(_id) { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
uni.showToast({ title: "查看评论", icon: "none", duration: 1000 });
},
// @forward
forward(_id) { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
uni.showToast({ title: "转发", icon: "none", duration: 1000 });
},
// @iconClick
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
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);
});
}
},
// @ondblclick
({ 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: "女"
},
imgList: [
'http://gips2.baidu.com/it/u=195724436,3554684702&fm=3028',
'http://gips3.baidu.com/it/u=3886271102,3123389489&fm=3028',
'http://gips0.baidu.com/it/u=3602773692,1512483864&fm=3028',
'http://gips3.baidu.com/it/u=119870705,2790914505&fm=3028',
'http://gips0.baidu.com/it/u=2298867753,3464105574&fm=3028',
'http://gips2.baidu.com/it/u=3944689179,983354166&fm=3028'
]
},
{
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: "女"
},
imgList: [
'http://gips2.baidu.com/it/u=195724436,3554684702&fm=3028',
'http://gips3.baidu.com/it/u=3886271102,3123389489&fm=3028',
'http://gips0.baidu.com/it/u=3602773692,1512483864&fm=3028',
'http://gips3.baidu.com/it/u=119870705,2790914505&fm=3028',
'http://gips0.baidu.com/it/u=2298867753,3464105574&fm=3028',
'http://gips2.baidu.com/it/u=3944689179,983354166&fm=3028'
]
}
];
}
}
}
</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%;
}
.progress {
/* #ifndef APP-NVUE */
width: 80%;
/* #endif */
height: 2px;
}
.progress-box {
/* #ifndef APP-NVUE */
width: 100%;
display: flex;
color: #fff;
/* #endif */
align-items: center;
justify-content: space-around;
}
.bottom {
margin: 10px;
}
.title {
color: #fff;
font-size: 13px;
}
.wh-full {
width: 100%;
height: 100%;
padding: 0;
margin: 0 auto;
overflow: hidden;
}
</style>
nvue代码示例
示例代码中用到了 uni-icons
请自行安装
<template>
<view class="wh-full" :style="screenFull">
<!-- 这里使用 v-if 是为了防止 请求后台时还没返回数据 导致组件初始化时没有视频资源 而出现错误 -->
<ml-swiper-v3 v-if="lists && lists.length > 0" :list="lists" @loadmore="loadmore" @ondblclick="" @onplay="onplay">
<!-- 右侧 -->
<template v-slot:right="{ item }">
<view class="right" v-if="item != null">
<!-- 用户头像 -->
<image v-if="item.author != null" class="userAvatar" :src="item.author.avatar" @click="avatarClick">
</image>
<!-- 点赞 -->
<view class="icon">
<uni-icons type="heart-filled" size="35" color="#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="#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-if="item != null">
<text class="title">{{ item.title }}</text>
</view>
</template>
</ml-swiper-v3>
</view>
</template>
<script>
export default {
data() {
return {
lists: [], // 资源数据
datas: {0:[],1:[]}, // 点赞、收藏等数据
count: 0, // 计数器
context: null,
}
},
computed: {
screenFull: function() {
const uniWin = uni.getSystemInfoSync();
const maxWidth = uniWin.windowWidth;
const maxHeight = uniWin.windowHeight;
return `width: ${maxWidth}px; height: ${maxHeight}px;`;
}
},
onHide() {
if (this.context) { this.context?.pause(); }
},
onLoad() {
this.datas["0"] = [], this.datas["1"] = [];
// (模拟请求后台获取数据)
uni.showToast({ title: "加载中...", icon: "loading", duration: 1500 });
setTimeout(() => {
let size = 50;
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: {
// @onplay
onplay(event) {
this.context = event?.context;
},
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) {
if (type == "0") {
uni.showToast({ title: "点赞", icon: "none", duration: 1000 });
} else if(type == "1") {
uni.showToast({ title: "收藏", icon: "none", duration: 1000 });
}
},
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: "女"
},
imgList: [
'http://gips2.baidu.com/it/u=195724436,3554684702&fm=3028',
'http://gips3.baidu.com/it/u=3886271102,3123389489&fm=3028',
'http://gips0.baidu.com/it/u=3602773692,1512483864&fm=3028',
'http://gips3.baidu.com/it/u=119870705,2790914505&fm=3028',
'http://gips0.baidu.com/it/u=2298867753,3464105574&fm=3028',
'http://gips2.baidu.com/it/u=3944689179,983354166&fm=3028'
]
},
{
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: "女"
},
imgList: [
'http://gips2.baidu.com/it/u=195724436,3554684702&fm=3028',
'http://gips3.baidu.com/it/u=3886271102,3123389489&fm=3028',
'http://gips0.baidu.com/it/u=3602773692,1512483864&fm=3028',
'http://gips3.baidu.com/it/u=119870705,2790914505&fm=3028',
'http://gips0.baidu.com/it/u=2298867753,3464105574&fm=3028',
'http://gips2.baidu.com/it/u=3944689179,983354166&fm=3028'
]
}
];
}
}
}
</script>
<style scoped>
.text {
font-size: 50px;
color: #ff5918;
}
.right {
width: 50px;
padding: 10px 0;
margin: 0;
}
.icon {
flex-wrap: wrap;
flex-direction: column;
justify-content: center;
margin: 8px 0;
/* #ifndef APP-NVUE */
display: flex;
margin: 8px auto;
/* #endif */
}
.icon-val {
color: #fff;
font-size: 13px;
text-align: center;
}
.userAvatar {
width: 40px;
height: 40px;
border-radius: 100%;
}
.progress {
/* #ifndef APP-NVUE */
width: 80%;
/* #endif */
height: 2px;
}
.progress-box {
/* #ifndef APP-NVUE */
width: 100%;
display: flex;
color: #fff;
/* #endif */
align-items: center;
justify-content: space-around;
}
.bottom {
margin: 10px;
}
.title {
color: #fff;
font-size: 13px;
}
.wh-full {
/* #ifndef APP-NVUE */
width: 100%;
height: 100%;
/* #endif */
padding: 0;
margin: 0;
overflow: hidden;
}
</style>
APP端完整示例
ml-swiper-v3 示例
属性和方法
/**
* mlSwiperV3 视频滑动组件
* @description 仿抖音短视频上下滑动组件,支持APP、小程序、H5
* @property {Array} list 资源数据列表
* @property {String} width 组件宽度,默认设备宽度
* @property {String} height 组件高度,默认设备高度
* @property {Number} criticalVal 临界值
* @property {Boolean} progress 是否显示进度条
* @property {Boolean} duration 是否显示播放时间
* @property {Boolean} customProgress 自定义进度条
* @property {Boolean} autoChange 是否自动切换下一个
* @property {Number} slideDuration 滑动动画时长
* @property {String} showFullscreen always | auto | none 显示全屏观看,APP仅支持 always | none
* @property {Boolean} customFullscreen 是否自定义全屏观看按钮
* @property {Number} startIndex 开始索引,从第 n 个资源开始
* @property {Boolean} customInitTip 自定义初始化文本插槽
* @property {String} initTipText 初始化提示文本,默认 初始化中...
*
* @event {Function} onchange 当视频上下滑动时,触发 onchange(event)事件:event = {index, context, video}
* @event {Function} onplay 当前视频播放时,触发 onplay(event)事件:event = {index, context, video, playing}
* @event {Function} onpause(event) 当前视频暂停时,触发 onpause事件 event = {index, context, video, playing}
* @event {Function} (event) 当前视频播放结束时,触发 onpause事件 event = {index, context, video, playing}
* @event {Function} 视频进度条变化时触发ontimeupdate事件,可用来自定义进度条 event事件
* @event {Function} 当前视频播放出现缓冲时,触发 事件 event = {index, context, video}
* @event {Function} onerror 当前视频播放出错时,触发 onerror事件 event = {index, context, video, error, event}
* @event {Function} onclick 当单击视频时触发onclick事件 event = {index, context, video, playing}
* @event {Function} 当双击视频时触发ondblclick事件 event = {index, context, video, playing}
* @event {Function} loadmore 当 list.length - currentIndex >= criticalVal时触发加载更多事件
* @event {Function} longTap 视频长按事件 event = {index, context, video, playing}
* @event {Function} fullscreenchange(event) 当视频进入和退出全屏时触发 event = {fullScreen, direction},direction取为 vertical 或 horizontal
* @event {Function} fullscreenclick(event) 全屏时的点击事件 event = { screenX, screenY, screenWidth, screenHeight }
*/
<template>
<view class="wh-full" :style="screenFull">
<!-- 这里使用 v-if 是为了防止 请求后台时还没返回数据 导致组件初始化时没有视频资源 而出现错误 -->
<ml-swiper-v3 v-if="lists && lists.length > 0" showFullscreen="always" @fullscreenchange="fullscreenchange"
@longTap="longTap" :list="lists" @loadmore="loadmore" @ondblclick="dblclick" @onplay="onplay">
<!-- 底部 -->
<template v-slot:fullscreen>
<view> <text>全屏</text> </view>
</template>
<template v-slot:bottom="{ item,index }">
<view class="bottom" v-if="item != null">
<view class="bottom-box">
<view class="author">
<image class="avatars" src="../../static/logo.png" mode="aspectFit"></image>
<text style="color: aliceblue;">作者</text>
</view>
<text style="color: aliceblue;border-radius: 20px;padding: 5px;background: #ff2720;">关注</text>
</view>
<view class="text-title">
<view class="">
<text style="color: aliceblue;">这里是标题</text>
</view>
<view class="">
<text style="color: grey;"> 【{{ index }}】{{ item.title }} 这里是副标题副标题副标题副标题副标题副标题副标题</text>
</view>
</view>
<view class="bottom-tool">
<input type="text" placeholder="说点什么" class="edit" />
<uni-icons type="heart-filled" size="35" color="#fff" />
<uni-icons type="star-filled" size="35" color="#fff" />
<uni-icons type="chat-filled" size="35" color="#fff" />
</view>
</view>
</template>
</ml-swiper-v3>
</view>
</template>
<script>
export default {
props: {},
data() {
return {
lists: [], // 资源数据
datas: {
0: [],
1: []
}, // 点赞、收藏等数据
count: 0, // 计数器
context: null,
}
},
computed: {
screenFull: function() {
const uniWin = uni.getSystemInfoSync();
const maxWidth = uniWin.windowWidth;
const maxHeight = uniWin.windowHeight;
return `width: ${maxWidth}px; height: ${maxHeight}px;`;
}
},
onHide() {
if (this.context) {
this.context?.pause();
}
},
created() {
this.datas["0"] = [], this.datas["1"] = [];
let list = [];
list.push({
videoId: 0,
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: "女"
}
});
this.getList().forEach((item) => list.push(item));
this.lists = [...this.lists, ...list];
console.log(list)
},
methods: {
longTap(event) {
console.log("组件长按事件:", event);
uni.showToast({
title: "组件长按事件",
icon: "none",
duration: 1000
});
},
fullscreenchange(event) {
console.log("进入/退出全屏事件:", event);
},
fullscreenclick(event) {
console.log("全屏点击事件:", event);
},
// @onplay
onplay(event) {
this.context = event?.context;
},
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) {
if (type == "0") {
uni.showToast({
title: "点赞",
icon: "none",
duration: 1000
});
} else if (type == "1") {
uni.showToast({
title: "收藏",
icon: "none",
duration: 1000
});
}
},
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);
});
}
},
dblclick({
video
}) {
console.log(video?.videoId);
uni.showToast({
title: "双击事件",
icon: "none",
duration: 1000
});
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>
.author {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
}
.edit {
width: 200px;
height: 35px;
margin-right: 20px;
border-radius: 15px;
background: #717171;
}
.bottom-tool {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
flex-wrap: wrap;
align-items: center;
}
.text-title {}
.avatars {
width: 30px;
height: 30px;
border-radius: 100%;
}
.bottom-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
}
.text {
font-size: 50px;
color: #ff5918;
}
.right {
width: 50px;
padding: 10px 0;
margin: 0;
}
.icon {
flex-wrap: wrap;
flex-direction: column;
justify-content: center;
margin: 8px 0;
/* #ifndef APP-NVUE */
display: flex;
margin: 8px auto;
/* #endif */
}
.icon-val {
color: #fff;
font-size: 13px;
text-align: center;
}
.userAvatar {
width: 40px;
height: 40px;
border-radius: 100%;
}
.progress {
/* #ifndef APP-NVUE */
width: 80%;
/* #endif */
height: 2px;
}
.progress-box {
/* #ifndef APP-NVUE */
width: 100%;
display: flex;
color: #fff;
/* #endif */
align-items: center;
justify-content: space-around;
}
.bottom {
/* #ifndef APP-NVUE */
width: 95%;
/* #endif */
flex: 1;
margin: 10px;
}
.title {
color: #fff;
font-size: 13px;
}
.wh-full {
/* #ifndef APP-NVUE */
width: 100%;
height: 100%;
/* #endif */
padding: 0;
margin: 0;
overflow: hidden;
}
</style>
ml-swiper-app 示例
属性和方法
/**
* mlSwiperApp 视频滑动组件
* @description APP端特有
* @property {Array} list 资源数据列表
* @property {Number} width 组件宽度,默认设备宽度
* @property {Number} height 组件高度,默认设备高度
* @property {Number} criticalVal 临界值
* @property {Boolean} progress 是否显示进度条
* @property {Boolean} autoChange 是否自动切换下一个
* @property {String} showFullscreen always | auto | none 显示全屏观看,APP仅支持 always | none
* @property {Boolean} custom 是否自定义视频组件
* @property {Boolean} autoplay 是否自动播放
* @property {Number} startIndex 开始索引,从第 n 个资源开始
*
* @event {Function} onchange 当视频上下滑动时,触发 onchange(event)事件:event = {index, context, video}
* @event {Function} onplay 当前视频播放时,触发 onplay(event)事件:event = {index, context, video, playing}
* @event {Function} onpause(event) 当前视频暂停时,触发 onpause事件 event = {index, context, video, playing}
* @event {Function} (event) 当前视频播放结束时,触发 onpause事件 event = {index, context, video, playing}
* @event {Function} 视频进度条变化时触发ontimeupdate事件,可用来自定义进度条 event事件
* @event {Function} 当前视频播放出现缓冲时,触发 事件 event = {index, context, video}
* @event {Function} onerror 当前视频播放出错时,触发 onerror事件 event = {index, context, video, error, event}
* @event {Function} onclick 当单击视频时触发onclick事件 event = {index, context, video, playing}
* @event {Function} 当双击视频时触发ondblclick事件 event = {index, context, video, playing}
* @event {Function} loadmore 当 list.length - currentIndex >= criticalVal时触发加载更多事件
* @event {Function} longTap 视频长按事件 event = {index, context, video, playing}
* @event {Function} fullscreenchange(event) 当视频进入和退出全屏时触发 event = {fullScreen, direction},direction取为 vertical 或 horizontal
* @event {Function} fullscreenclick(event) 全屏时的点击事件 event = { screenX, screenY, screenWidth, screenHeight }
*
* 插槽:
* custom 为 true 时:
* default:可以自定义 video 视频组件;参数:item(当前数据)、index(当前索引)
*
* custom 为 false 时:
* right:右侧插槽;参数:item(当前数据)、index(当前索引)
* bottom:底部插槽;参数:item(当前数据)、index(当前索引)
* default:默认插槽;参数:item(当前数据)、index(当前索引)
*
* progress 为 false 并且 custom 为 false 时:
* progress:自定义进度条;参数:item(当前数据)、index(当前索引)
*/
如果方法中的方法名为空,说明方法名被浏览器转义掉了,为保证可以正常运行,请前往 readme.md 文档中复制响应源码测试
<template>
<view :style="`width:${width}px;height:${height}px;overflow:hidden;`">
<ml-swiper-app v-if="list.length > 2" :list="list" :width="width" :height="height" :custom="custom"
:progress="progress" :criticalVal="criticalVal" :autoplay="autoplay" :startIndex="startIndex"
:autoChange="autoChange" @change="onChange" @toupper="toupper" @tolower="tolower" @loadmore="loadmore"
@ondblclick="dblclick" @onplay="onPlay" @onpause="onPause" @onended="ended" @onError="errored"
@onWaiting="waiting" @onTimeUpdate="timeUpdated" @fullscreenchange="fullscreenchange"
@fullscreenclick="fullscreenclick">
<!-- custom="true":自定义 video 组件 -->
<template v-if="custom" v-slot:default="{item}">
<video :src="item.url" :title="item.title" :poster="item.poster" :autoplay="false" :controls="false"
:loop="true" :show-center-play-btn='true' play-btn-position="center" :show-loading="false"
:enable-progress-gesture="false" object-fit="contain" :style="`width:${width}px;height:${height}px;`"></video>
</template>
<!-- custom="false":使用默认 video 组件,当 custom="true",以下插槽失效,需要自定义实现 -->
<!-- 默认插槽:default = {item: "当前资源数据", index: "当前资源索引"} -->
<template v-slot:default="{ index }">
<view> <text style="color: #fff;">{{ index + 1 }} / {{ list.length }}</text> </view>
</template>
<!-- 右侧插槽:right = {item: "当前资源数据", index: "当前资源索引"} -->
<template v-slot:right="{ item }">
<view class="right">
<image class="userAvatar" :src="item?.author?.avatar"></image>
<!-- 喜欢 -->
<view class="icon">
<uni-icons type="heart-filled" size="35" color="#ff0004" />
<text class="icon-val">666</text>
</view>
<!-- 评论 -->
<view class="icon">
<uni-icons type="chat-filled" size="35" color="#fff" />
<text class="icon-val">668</text>
</view>
<!-- 收藏 -->
<view class="icon">
<uni-icons type="star-filled" size="35" color="#ff0" />
<text class="icon-val">888</text>
</view>
<!-- 转发 -->
<view class="icon">
<uni-icons type="redo-filled" size="35" color="#fff" />
<text class="icon-val">999</text>
</view>
</view>
</template>
<!-- 底部插槽:bottom = {item: "当前资源数据", index: "当前资源索引"} -->
<template v-slot:bottom="{ item }">
<view :style="`width:${width}px;`">
<text style="color: #fff;">{{ item.title }}</text>
</view>
</template>
<!-- progress="false":不使用默认进度条,自定义进度条,需要配合 @ontimeupdate 事件获取当前播放时间 -->
</ml-swiper-app>
</view>
</template>
<script>
import mlSwiperApp from '../../uni_modules/ml-swiper-v3/components/ml-swiper-v3/ml-swiper-app.vue';
export default {
components: {
mlSwiperApp
},
data() {
return {
list: [], // 视频数据列表:{url:"视频资源地址", poster:"预览图,尽量给出,否则上下滑动时会黑屏", title:"视频标题"}
custom: false, // 是否自定义 video 组件, 默认false
progress: true, // 是否显示进度条,默认 true
autoChange: true, // 是否自动播放下一个
criticalVal: 2, // 临界值,默认 2
autoplay: true, // 初次加载时视频是否自动播放
startIndex: 0, // 开始索引,从第 n 个资源开始,默认0
width: uni.getSystemInfoSync().windowWidth, // 组件宽度
height: uni.getSystemInfoSync().windowHeight // 组件高低
}
},
created() {
this.list = this.getList();
},
mounted() {},
onHide() { // 如果需要离开页面暂停播放,尽量提供 onHide
this.pause();
},
onUnload() {
this.pause();
this.list = [];
this.context = null;
},
methods: {
onChange(e) { // onChange
console.log(" === onChange 滑动 ===> ", e);
console.log(this.custom)
console.log("\r\n");
if (this.custom) {
this.play();
}
},
toupper(e) { // toupper
console.log(" === toupper 到顶了 ===> ", e);
console.log("\r\n");
uni.showToast({
title: "到顶了",
icon: 'none'
});
},
tolower(e) { // tolower
console.log(" === tolower 到底了 ===> ", e);
console.log("\r\n");
uni.showToast({
title: "到底了",
icon: 'none'
});
},
loadmore(e) { // loadmore
console.log(" === loadmore 加载更多 ===> ", e);
console.log("\r\n");
uni.showToast({
title: "加载更多",
icon: 'none'
});
this.list = [...this.list, ...this.getList()];
},
dblclick(e) { // dblclick
console.log(" === dblclick 双击 ===> ", e);
console.log("\r\n");
uni.showToast({
title: "双击",
icon: 'none'
});
},
onPlay(e) { // onPlay
this.context = e.context;
console.log(" === onPlay 开始播放 ===> ", e);
console.log("\r\n");
},
onPause(e) { // onPause
console.log(" === onPause 暂停播放 ===> ", e);
console.log("\r\n");
},
ended(e) { // ended
console.log(" === ended 播放结束 ===> ", e);
console.log("\r\n");
},
errored(e) { // errored
console.error(e);
},
waiting(e) { // waiting
console.log(" === waiting 出现缓冲 ===> ", e);
console.log("\r\n");
},
timeUpdated(e) { // timeUpdated
console.log(" === timeUpdated 更新时长 ===> ", e);
},
fullscreenchange(e) { // fullscreenchange
console.log(" === fullscreenchange 全屏变更 ===> ", e);
console.log("\r\n");
},
fullscreenclick(e) { // fullscreenclick
console.log(" === fullscreenclick 全屏点击 ===> ", e);
console.log("\r\n");
},
pause() { // pause
if (this.context?.pause) {
this.context.pause();
}
},
play() { // play
if (this.context?.play) {
this.context.play();
}
},
// getList
getList() {
return [{
videoId: Math.ceil(Math.random() * 689),
likes: Math.ceil(Math.random() * 689),
stars: Math.ceil(Math.random() * 689),
isLike: 0,
title: `抖音美女主播,JK超短裙学生妆美女跳舞展示,爱了爱了。`,
poster: "http://gips2.baidu.com/it/u=195724436,3554684702&fm=3028",
url: "https://alimov2.a.yximgs.com/upic/2020/07/02/14/BMjAyMDA3MDIxNDUyMDlfOTExMjIyMjRfMzE1OTEwNjAxNTRfMV8z_b_Bf3005d42ce9c01c0687147428c28d7e6.mp4",
uploadTime: "2023-11-08 19:41",
ipLocation: "上海",
author: {
authorId: 101,
avatar: "http://gips2.baidu.com/it/u=195724436,3554684702&fm=3028",
nickName: "陌路",
genderName: "男"
}
},
{
videoId: Math.ceil(Math.random() * 689),
likes: Math.ceil(Math.random() * 689),
stars: Math.ceil(Math.random() * 689),
isLike: 0,
title: `图片列表测试。`,
poster: "http://gips3.baidu.com/it/u=3886271102,3123389489&fm=3028",
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: "http://gips3.baidu.com/it/u=3886271102,3123389489&fm=3028",
nickName: "陌路",
genderName: "男"
},
imgList: [
'http://gips2.baidu.com/it/u=195724436,3554684702&fm=3028',
'http://gips3.baidu.com/it/u=3886271102,3123389489&fm=3028',
'http://gips0.baidu.com/it/u=3602773692,1512483864&fm=3028',
'http://gips3.baidu.com/it/u=119870705,2790914505&fm=3028',
'http://gips0.baidu.com/it/u=2298867753,3464105574&fm=3028',
'http://gips2.baidu.com/it/u=3944689179,983354166&fm=3028'
]
},
{
videoId: Math.ceil(Math.random() * 689),
likes: Math.ceil(Math.random() * 689),
stars: Math.ceil(Math.random() * 689),
isLike: 0,
title: `御姐美女抖音作品,来个自拍视频把,好美啊。`,
poster: "http://gips0.baidu.com/it/u=3602773692,1512483864&fm=3028",
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: "http://gips0.baidu.com/it/u=3602773692,1512483864&fm=3028",
nickName: "御姐呀",
genderName: "女"
}
},
{
videoId: Math.ceil(Math.random() * 689),
likes: Math.ceil(Math.random() * 689),
stars: Math.ceil(Math.random() * 689),
isLike: 0,
title: `图片列表测试。`,
poster: "http://gips3.baidu.com/it/u=119870705,2790914505&fm=3028",
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: "http://gips3.baidu.com/it/u=119870705,2790914505&fm=3028",
nickName: "陌路",
genderName: "男"
},
imgList: [
'http://gips2.baidu.com/it/u=195724436,3554684702&fm=3028',
'http://gips3.baidu.com/it/u=3886271102,3123389489&fm=3028',
'http://gips0.baidu.com/it/u=3602773692,1512483864&fm=3028',
'http://gips3.baidu.com/it/u=119870705,2790914505&fm=3028',
'http://gips0.baidu.com/it/u=2298867753,3464105574&fm=3028',
'http://gips2.baidu.com/it/u=3944689179,983354166&fm=3028'
]
},
{
videoId: Math.ceil(Math.random() * 689),
likes: Math.ceil(Math.random() * 689),
stars: Math.ceil(Math.random() * 689),
isLike: 0,
title: `抖音主播可爱妹子新学的舞蹈,超可爱的美女主播。`,
poster: "http://gips0.baidu.com/it/u=2298867753,3464105574&fm=3028",
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: "http://gips0.baidu.com/it/u=2298867753,3464105574&fm=3028",
nickName: "野花猫",
genderName: "女"
}
},
{
videoId: Math.ceil(Math.random() * 689),
likes: Math.ceil(Math.random() * 689),
stars: Math.ceil(Math.random() * 689),
isLike: 0,
title: `多个美女带着遮阳帽出去散步自拍视频,好好看。`,
poster: "http://gips2.baidu.com/it/u=3944689179,983354166&fm=3028",
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: "http://gips2.baidu.com/it/u=3944689179,983354166&fm=3028",
nickName: "蓝姬",
genderName: "女"
}
}
];
}
}
}
</script>
<style scoped lang="scss">
.text {
font-size: 50px;
color: #ff5918;
}
.right {
width: 50px;
padding: 10px 0;
margin: 0;
}
.icon {
display: flex;
flex-wrap: wrap;
flex-direction: column;
justify-content: center;
}
.icon-val {
color: #fff;
font-size: 13px;
text-align: center;
}
.userAvatar {
width: 40px;
height: 40px;
border-radius: 100%;
}
</style>