效果图
<template>
<view class="swiper-container" ref="root" @touchstart="onTouchStart" @touchend="onTouchEnd">
<view @click="evtChangeIndex(index)" class="side" v-for="(item, index) in state.source" :key="index"
:style="item.sty" :class="item.className">
<img :src="item?.img" alt="">
</view>
</view>
</template>
<script>
import { ref, computed, reactive, watch, onMounted } from 'vue';
export default {
props: {
/**
* 轮播数据来源
*/
source: {
type: Array,
default: () => [
{ img: require('@/assets/imgs/test/1.png') },
{ img: require('@/assets/imgs/test/1.png') },
{ img: require('@/assets/imgs/test/1.png') },
{ img: require('@/assets/imgs/test/1.png') },
{ img: require('@/assets/imgs/test/1.png') },
],
},
/**
* 初始激活的索引
*/
initial: {
type: Boolean,
default: 0
},
/**
* 自动播放时间
*/
interval: {
type: Number,
default: 3500
},
},
emits: ['update:active'],
setup(props, { emit }) {
/**
* 轮播数据源
*/
let source = props.source;
/**
* 轮播数据源长度差
*/
let diff = source.length - 5;
// 不足5个数据,补全
if (diff < 0) {
//缺几个补几个
diff = Math.abs(diff);
source.slice(0, diff).forEach(item => {
source.push(item);
})
}
/**
* 处理每一项的样式
* @param {*} initial 当前项
* @param {*} source 数据源
*/
const computed = (initial, source) => {
// 当前项的索引
let len = source.length;
//先处理中间值
initial = initial < 0 ? 0 : (initial >= len ? len - 1 : initial);
//5个项的temp
let temp1 = initial - 2;
let temp2 = initial - 1;
let temp3 = initial;
let temp4 = initial + 1;
let temp5 = initial + 2;
//处理边界值
temp1 = temp1 < 0 ? len + temp1 : temp1;
temp2 = temp2 < 0 ? len + temp2 : temp2;
//超界行为
temp4 = temp4 >= len ? temp4 - len : temp4;
temp5 = temp5 >= len ? temp5 - len : temp5;
//计算每一项样式
return source.map((item, index) => {
let transform = 'translate(-50%, -50%) scale(0.55)',
zIndex = 0,
className = 'side';
switch (index) {
//中间的
case temp3:
zIndex = 3;
className = 'side active';
transform = 'translate(-50%, -50%) scale(1)';
break;
case temp1:
zIndex = 1;
className = 'side active';
transform = 'translate(-160%, -50%) scale(0.8)';
break;
case temp2:
zIndex = 2;
className = 'side active';
transform = 'translate(-110%, -50%) scale(0.9)';
break;
case temp4:
zIndex = 2;
className = 'side active';
transform = 'translate(10%, -50%) scale(0.9)';
break;
case temp5:
zIndex = 1;
className = 'side active';
transform = 'translate(60%, -50%) scale(0.8)';
break;
}
item.sty = {
zIndex,
transform
}
item.className = className;
return item;
})
}
source = computed(props.initial, source);
/**
* 构建响应式
*/
const state = reactive({
initial: props.initial,
source
})
let autoTimer = null;
/**
* 监听激活数据变化
*/
watch(() => state.initial, (initial, preInitial) => {
state.source = computed(initial, state.source);
})
/**
* 自动轮播
*/
const autoPlay = () => {
autoTimer = setInterval(() => {
state.initial++;
//边界处理
if (state.initial >= state.source.length) {
state.initial = 0;
}
}, props.interval);
}
/**
* 手势处理逻辑
*/
let startX = 0;
let endX = 0;
const onTouchStart = (e) => {
startX = e.touches[0].clientX;
//手势关闭自动滚动
stopAutoPlay();
};
const onTouchEnd = (e) => {
endX = e.changedTouches[0].clientX;
if (startX > endX + 50) {
// 左滑
change('right');
} else if (startX < endX - 50) {
// 右滑
change('left');
}
//手势识别后重启
autoPlay();
};
/**
* 停止自动播放
*/
const stopAutoPlay = () => {
clearInterval(autoTimer);
}
let root = ref(null);
//开启自动轮播
onMounted(() => {
//开启自动轮播
autoPlay();
let box = root.value;
// box.onmouseenter = function () {
// clearInterval(autoPlay);
// }
// box.onmouseleave = function () {
// autoPlay();
// }
})
/**
* 切换
* @param dir left:上一张,right:下一张
*/
const change = (dir) => {
if (dir == 'left') {
state.initial--;
if (state.initial < 0) {
state.initial = state.source.length - 1;
}
}
if (dir == 'right') {
state.initial++;
//超出边界定向到0
if (state.initial >= state.source.length) {
state.initial = 0;
}
}
}
/**
* 轮播切换 具体值
*/
const evtChangeIndex = (index) => {
state.initial = index;
//重启定时
stopAutoPlay();
autoPlay();
}
return {
state,
root,
change,
evtChangeIndex,
onTouchStart,
onTouchEnd
}
},
};
</script>
<style lang="scss">
.swiper-container {
width: 360px;
display: flex;
height: 200px;
justify-content: center;
/* 水平方向居中 */
align-items: center;
/* 垂直方向居中 */
position: relative;
background: transparent;
overflow: hidden;
/* 隐藏超出部分 */
.side {
position: absolute;
width: 35%;
height: 200px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(1);
transition: 0.5s linear;
border-radius: 3px;
overflow: hidden;
img {
height: 100%;
width: 100%;
}
}
}
</style>