抖音效果图
本内容主要实现了滑动视频组件、首个视频自动播放、预加载、实现加载更多,超高性能,
前言:最近在做短剧,于是就在网上找了很多不错的例子,但是不是很完美,基本上都比较卡顿,我也是在站在巨人的肩膀上优化了一下。本片主要基于vue3、setup和ts开发的。
相关参考:
video | uni-app官网 (dcloud.net.cn)
uni.createVideoContext(videoId, this) | uni-app官网 (dcloud.net.cn)
项目结构:
主要组件:代码里逻辑很清晰,就不再赘述了。video-play.vue
<template>
<swiper class="video-swiper" circular @change="swiperChange" :current="state.current" :vertical="true"
duration="800">
<swiper-item v-for="(item, index) in state.displaySwiperList" :key="index">
<view class="swiper-item" @click="handleClick">
<video :id="`video${index}`" :controls="controls" :show-fullscreen-btn="false" :autoplay="false"
:loop="loop" @ended="ended" @controlstoggle="controlstoggle" @play="onPlay" @error="emits('error')"
class="video-player" :src="item.src" v-if="index === 0 || !state.isFirstLoad"></video>
<slot :item="item"></slot>
</view>
</swiper-item>
</swiper>
</template>
<script lang="ts" setup>
import { getCurrentInstance, watch, onMounted, onUnmounted } from "vue";
import { useState } from './moudle'
interface IvideoItem {
src : string;//视频链接
title : string;
id : string;
}
interface Iprops {
videoList : Array<IvideoItem>
loop ?: boolean //是否循环播放一个视频
controls ?: boolean
autoplay ?: boolean
autoChange ?: boolean //是否自动滚动播放
loadMoreOffsetCount ?: number //滚动加载阈值(即播放到剩余多少个之后触发加载更多
}
const emits = defineEmits<{
(e : 'play',value : Event) : void
(e : 'error') : void
(e : 'loadMore') : void
(e : 'change',{
index: number,
detail: any,
}) : void
(e : 'controlstoggle',value : any) : void
(e : 'click',value : Event) : void
(e : 'ended') : void
}>();
const props = withDefaults(defineProps<Iprops>(), {
videoList: () => [],
loop: true,
controls: true,
autoplay: true,
autoChange: true,
loadMoreOffsetCount: 2
})
const state = useState()
const initVideoContexts = () => {
state.videoContexts = [
uni.createVideoContext("video0", getCurrentInstance()),
uni.createVideoContext("video1", getCurrentInstance()),
uni.createVideoContext("video2", getCurrentInstance()),
];
};
const onPlay = (e : Event) => {
emits("play", e);
};
function handleClick(e : Event) {
state.toggleShow = !state.toggleShow;
emits("click", e);
}
function ended() {
// 自动切换下一个视频
if (props.autoChange) {
if (state.displayIndex < 2) {
state.current = state.displayIndex + 1;
} else {
state.current = 0;
}
}
emits("ended");
}
/**
* 初始一个显示的swiper数据
* @originIndex 从源数据的哪个开始显示默认0,如从其他页面跳转进来,要显示第n个,这个参数就是他的下标
*/
function initSwiperData(originIndex = state.originIndex) {
const originListLength = state.originList.length; // 源数据长度
const displayList = [];
displayList[state.displayIndex] = state.originList[originIndex];
displayList[state.displayIndex - 1 == -1 ? 2 : state.displayIndex - 1] =
state.originList[
originIndex - 1 == -1 ? originListLength - 1 : originIndex - 1
];
displayList[state.displayIndex + 1 == 3 ? 0 : state.displayIndex + 1] =
state.originList[originIndex + 1 == originListLength ? 0 : originIndex + 1];
state.displaySwiperList = displayList;
if (state.oid >= state.originList.length) {
state.oid = 0;
}
if (state.oid < 0) {
state.oid = state.originList.length - 1;
}
// 暂停所有视频
state.videoContexts.map((item : any) => item?.stop());
setTimeout(() => {
// 当前视频
if (props.autoplay) {
state.videoContexts[state.displayIndex].play()
}
}, 600);
// 数据改变
emits("change", {
index: originIndex,
detail: state.originList[originIndex],
});
// 加载更多
var pCount = state.originList.length - props.loadMoreOffsetCount;
if (originIndex == pCount) {
emits("loadMore");
}
}
/**
* swiper滑动时候
*/
function swiperChange(event : any) {
const { current } = event.detail;
state.isFirstLoad = false;
const originListLength = state.originList.length; // 源数据长度
// 向后滚动
if (state.displayIndex - current == 2 || state.displayIndex - current == -1) {
state.originIndex =
state.originIndex + 1 == originListLength ? 0 : state.originIndex + 1;
state.displayIndex =
state.displayIndex + 1 == 3 ? 0 : state.displayIndex + 1;
state.oid = state.originIndex - 1;
initSwiperData(state.originIndex);
}
// 如果两者的差为-2或者1则是向前滑动
else if (
state.displayIndex - current == -2 ||
state.displayIndex - current == 1
) {
state.originIndex =
state.originIndex - 1 == -1
? originListLength - 1
: state.originIndex - 1;
state.displayIndex =
state.displayIndex - 1 == -1 ? 2 : state.displayIndex - 1;
state.oid = state.originIndex + 1;
initSwiperData(state.originIndex);
}
state.toggleShow = true;
}
function controlstoggle(e : any) {
state.showControls = e.detail.show;
emits("controlstoggle", e);
}
watch(
() => props.videoList,
() => {
if (props.videoList?.length) {
state.originList = props.videoList;
if (state.isFirstLoad || !state.videoContexts?.length) {
initSwiperData();
initVideoContexts();
}
}
},
{
immediate: true,
}
);
let loadTimer : any = null;
onMounted(() => {
// 为了首次只加载一条视频(提高首次加载性能),延迟加载后续视频
loadTimer = setTimeout(() => {
state.isFirstLoad = false;
clearTimeout(loadTimer);
}, 3000);
})
onUnmounted(() => {
clearTimeout(loadTimer);
})
</script>
<style lang="scss" scoped>
.video-swiper {
width: 100%;
height: 100vh;
background-color: #000;
swiper-item {
.video-player {
width: 100%;
height: 100vh;
}
}
}
</style>
状态管理:
moudle.ts
import { reactive } from "vue"
const useState=()=>{
return reactive({
originList: [] as any, // 源数据
displaySwiperList: [] as any, // swiper需要的数据
displayIndex: 0, // 用于显示swiper的真正的下标数值只有:0,1,2。
originIndex: 0, // 记录源数据的下标
current: 0,
oid: 0,
showControls: "",
toggleShow: true, // 显示面板
videoContexts: [] as any,
isFirstLoad: true,
})
}
export {useState}
引用逻辑:文章来源:https://www.toymoban.com/news/detail-758113.html
<template>
<div class="video-container">
<video-play :video-list="state.videoList" @loadMore="loadMore" >
<!-- 此处data是从子组件获取的数据 不明白参考https://cn.vuejs.org/guide/components/slots.html#dynamic-slot-names-->
<template v-slot="data">
<view class="video-title"> {{ data.item.title }} </view>
</template>
</video-play>
</div>
</template>
<script lang="ts" setup>
import { reactive } from "vue";
// 导入组件
const state = reactive({
videoList: [
{
src: "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4",
id: "1",
title: "亲近大自然"
},
{
src: "https://img.chenggua.com/mnzcdcjgs.mp4",
id: "2",
title: "亲近大自然"
},
{
src: "https://img.chenggua.com/mnzcdcjgs.mp4",
id: "3",
title: "亲近大自然"
},
{
src: "http://vjs.zencdn.net/v/oceans.mp4",
id: "4",
title: "亲近大自然"
},
{
src: "https://xjc.demo.hongcd.com/uploads/20210128/0c64cbeea28b10c06eee8728c762449e.mp4",
id: "5",
title: "亲近大自然"
},
{
src: "https://xjc.demo.hongcd.com/uploads/20210327/1b72e1b6153cd29df07f5449991e8083.mp4",
id: "6",
title: "亲近大自然"
},
{
src: "https://xjc.demo.hongcd.com/uploads/20230214/7e1a0baaebc4e656bbbfbc44d7a55a60.mp4",
id: "7",
title: "亲近大自然"
},
],
});
const loadMore = () => {
state.videoList.push({
src: "https://img.chenggua.com/mnzcdcjgs.mp4",
id: state.videoList.length+"",
title: '我是加载更多加载更多'+state.videoList.length
})
};
</script>
<style lang="scss">
.video-title {
position: absolute;
left: 30rpx;
top: 50rpx;
color: #fff;
}
</style>
以上测试逻辑都是基于小程序测试的,希望对于您有所帮助。文章来源地址https://www.toymoban.com/news/detail-758113.html
到了这里,关于利用uniapp中模仿抖音、滑动视频组件、首个视频自动播放、预加载、实现加载更多,超高性能的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!