uniapp vue3-swiper(概念版)
使用三个元素,实现无限轮播
概念就是:左滑结束第一个元素移动到最后;右滑结束最有一个元素移动到第一个。
*没有自动轮播功能哦,没写,因为我项目中没需要自动轮播功能,应该是加个定时器就可以了
使用示例
<!-- 使用questionSwiper.jump(index,timing)跳转; 或者直接修改current属性,都可以 -->
<b-question-swiper :items="exercise.questions" ref="questionSwiper" :current="exercise.index" @change="onIndexChange">
<template v-slot:default="{item,index}">
<b-question :value="item" @selected="onSelectOption" :numberName="(index+1)+'/'+exercise.questions.length+'、'" :result="showResult"></b-question>
</template>
</b-question-swiper>
就不花时间寒暄了,直接贴完整代码。
<template>
<view @touchstart="_onTouchStart($event)" @touchmove="_onTouchMove($event)" @touchend="_onTouchEnd"
class="custom-swiper">
<template v-for="(item,index) in list" :key="index">
<view class="item" v-if="item.item" v-show="item.show" :animation="item.animation">
<slot :item="item.item" :index="item.index"></slot>
</view>
</template>
</view>
</template>
<script lang="ts" setup>
import {computed, onMounted, ref, watch} from 'vue'
// 暂时不知道怎么更好的设置元素宽度,只能先满宽了
const windowWidth = uni.getSystemInfoSync().windowWidth;
interface Props {
duration?: number;
items: any[] | Array<any>;
current?: number,
}
const emits = defineEmits<{ (e: 'change', index: number): void }>()
const props = withDefaults(defineProps<Props>(), {duration: 500, items: () => [], current: 0});
const duration = computed<number>(() => props.duration)
watch(() => props.current, (newValue, oldValue) => {
let centerData = leftItem.value.translateX === 0 ? leftItem : centerItem.value.translateX === 0 ? centerItem : rightItem;
if (newValue !== centerData.value.index) {
jump(newValue)
}
console.log(newValue, centerData.value.index)
})
interface _ItemVO {
item?: any;
index: number;
animation: any;
translateX: number;
show: boolean;
}
const leftItem = ref<_ItemVO>({index: -1, animation: {}, translateX: -windowWidth, show: true})
const centerItem = ref<_ItemVO>({index: 0, animation: {}, translateX: -windowWidth, show: true})
const rightItem = ref<_ItemVO>({index: 1, animation: {}, translateX: -windowWidth, show: true})
// 一直保持3条可循环数据,元素可以为空,为空时不执行slot展示
const list = computed<_ItemVO[]>(() => {
let leftData = leftItem.value.translateX === -windowWidth ? leftItem : centerItem.value.translateX === -windowWidth ? centerItem : rightItem;
let centerData = leftItem.value.translateX === 0 ? leftItem : centerItem.value.translateX === 0 ? centerItem : rightItem;
let rightData = leftItem.value.translateX === windowWidth ? leftItem : centerItem.value.translateX === windowWidth ? centerItem : rightItem;
if (leftData.value.index >= 0) {
leftData.value.item = props.items[leftData.value.index]
} else {
leftData.value.item = null;
}
centerData.value.item = props.items[centerData.value.index];
if (rightData.value.index <= props.items.length - 1) {
rightData.value.item = props.items[rightData.value.index]
} else {
rightData.value.item = null;
}
return [leftItem.value, centerItem.value, rightItem.value];
});
// 左侧动画
const leftAnimation = uni.createAnimation({timingFunction: 'ease'})
const centerAnimation = uni.createAnimation({timingFunction: 'ease'})
const rightAnimation = uni.createAnimation({timingFunction: 'ease'})
// 是否移动中,移动中不能再移动,等待动画结束
const moving = ref<boolean>(false);
onMounted(() => {
leftAnimation.translateX(-windowWidth).step({duration: duration.value});
leftItem.value.animation = leftAnimation.export();
leftItem.value.translateX = -windowWidth;
leftItem.value.index = props.current - 1;
centerAnimation.translateX(0).step({duration: duration.value});
centerItem.value.animation = centerAnimation.export();
centerItem.value.translateX = 0;
centerItem.value.index = props.current;
rightAnimation.translateX(windowWidth).step({duration: duration.value});
rightItem.value.animation = rightAnimation.export();
rightItem.value.translateX = windowWidth;
rightItem.value.index = props.current + 1;
})
const touchData = ref<{ start: number, move: number }>({start: 0, move: 0});
function _onTouchStart(event: TouchEvent) {
touchData.value.start = event.touches[0].clientX;
}
function _onTouchMove(event: TouchEvent) {
if (moving.value) {
return;
}
touchData.value.move = touchData.value.start - event.touches[0].clientX;
leftAnimation.translateX(leftItem.value.translateX - touchData.value.move).step({duration: 0});
leftItem.value.animation = leftAnimation.export();
centerAnimation.translateX(centerItem.value.translateX - touchData.value.move).step({duration: 0});
centerItem.value.animation = centerAnimation.export();
rightAnimation.translateX(rightItem.value.translateX - touchData.value.move).step({duration: 0});
rightItem.value.animation = rightAnimation.export();
}
/**
*
* @param event 兼容触摸控制
* @param transitionTime 动画耗时
* @param isLast 是否是最后一个动画,否则不执行缓慢滑动
*/
function _onTouchEnd(event: TouchEvent | null, transitionTime: number = duration.value, isLast: boolean = true): Promise<void> {
if (touchData.value.move === 0 || moving.value) {
return new Promise<void>((ok) => ok());
}
let leftData = leftItem.value.translateX === -windowWidth ? leftItem : centerItem.value.translateX === -windowWidth ? centerItem : rightItem;
let centerData = leftItem.value.translateX === 0 ? leftItem : centerItem.value.translateX === 0 ? centerItem : rightItem;
let rightData = leftItem.value.translateX === windowWidth ? leftItem : centerItem.value.translateX === windowWidth ? centerItem : rightItem;
leftItem.value.show = true;
centerItem.value.show = true;
rightItem.value.show = true;
let left = touchData.value.move > 0;
let reset = Math.abs(touchData.value.move) <= 20;
if (!left && centerData.value.index <= 0) {
reset = true;
}
if (centerData.value.index >= props.items.length - 1 && left) {
reset = true;
}
if (reset) {
leftAnimation.translateX(leftItem.value.translateX).step({duration: 100});
leftItem.value.animation = leftAnimation.export();
centerAnimation.translateX(centerItem.value.translateX).step({duration: 100});
centerItem.value.animation = centerAnimation.export();
rightAnimation.translateX(rightItem.value.translateX).step({duration: 100});
rightItem.value.animation = rightAnimation.export();
return new Promise<void>((ok) => ok());
}
moving.value = true;
let add = left ? -windowWidth : windowWidth;
let leftTranslateX = leftData.value.translateX + add;
let centerTranslateX = centerData.value.translateX + add;
let rightTranslateX = rightData.value.translateX + add;
let timingFunction: 'linear' | 'ease' | 'ease-in' | 'ease-in-out' | 'ease-out' | 'step-start' | 'step-end' = isLast ? 'ease' : 'linear';
leftAnimation.translateX(leftTranslateX).step({duration: transitionTime, timingFunction: timingFunction});
leftData.value.animation = leftAnimation.export();
centerAnimation.translateX(centerTranslateX).step({duration: transitionTime, timingFunction: timingFunction});
centerData.value.animation = centerAnimation.export();
rightAnimation.translateX(rightTranslateX).step({duration: transitionTime, timingFunction: timingFunction});
rightData.value.animation = rightAnimation.export();
return new Promise<void>((ok) => {
setTimeout(() => {
// 动画执行完成
centerData.value.translateX = centerTranslateX;
if (left) {
leftData.value.show = false;
leftData.value.translateX = rightData.value.translateX;
leftAnimation.translateX(leftData.value.translateX).step({duration: 0});
leftData.value.animation = leftAnimation.export();
leftData.value.show = true
rightData.value.translateX = rightTranslateX;
} else {
rightData.value.show = false;
rightData.value.translateX = leftData.value.translateX;
rightAnimation.translateX(rightData.value.translateX).step({duration: 0});
rightData.value.animation = rightAnimation.export();
rightData.value.show = true
leftData.value.translateX = leftTranslateX;
}
if (left) {
leftData.value.index = rightData.value.index + 1;
} else {
rightData.value.index = leftData.value.index - 1;
}
touchData.value.move = 0;
moving.value = false;
// 让动画飞一会儿
setTimeout(() => {
_change()
ok();
}, 1)
}, transitionTime - 2)
})
}
function _change() {
let centerData = leftItem.value.translateX === 0 ? leftItem : centerItem.value.translateX === 0 ? centerItem : rightItem;
emits('change', centerData.value.index)
}
function jump(index: number, transitionTime: number = 500): Promise<void> {
let centerData = leftItem.value.translateX === 0 ? leftItem : centerItem.value.translateX === 0 ? centerItem : rightItem;
let time = transitionTime / Math.abs(index - centerData.value.index);
return _jump(index, time)
}
function _jump(index: number, time: number): Promise<void> {
return new Promise<void>((ok) => {
let centerData = leftItem.value.translateX === 0 ? leftItem : centerItem.value.translateX === 0 ? centerItem : rightItem;
if (index < 0 || index > props.items.length - 1 || index == centerData.value.index) {
return ok();
}
let isLast = index === centerData.value.index - 1 || index == centerData.value.index + 1;
if (centerData.value.index < index) {
// 向后跳转
touchData.value.move = 100;
_onTouchEnd(null, time, isLast).then(() => {
_jump(index, time).then(ok)
})
} else if (centerData.value.index > index) {
touchData.value.move = -100;
_onTouchEnd(null, time, isLast).then(() => {
_jump(index, time).then(ok)
})
} else {
return ok();
}
})
}
</script>
<style scoped>
.custom-swiper {
position: relative;
}
.custom-swiper .item {
position: absolute;
}
</style>
如果不使用uniapp的话,需要把代码中uni.*相关功能修改一下,但这可能比较麻烦。因为动画使用的uni.createAnimation函数
由于本人并不是前端专业,可能有一些Bug。如果有,请留言。但不一定会改哈哈