yfs/components/tiner-swiper-loop/tiner-swiper-loop.vue
2025-05-03 12:51:13 +08:00

349 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="scroll-view" @touchstart="onTouchStart" @touchmove="onTouchMove" @touchend="onTouchEnd">
<view class="view-list" :style="scrollOffsetStyle">
<view class="list-item" v-for="(item,index) in suffixItems" :key="index+'left'"
:style="(index-excessCount == curIndex && itemSelectBig) ? itemParentSelectStyle : itemParentNomalStyle"
@click="onItemClick(-excessCount,index)">
<view :class="{'item-checked ':itemSelectBig && (curIndex == index - excessCount)}"
:style="itemAnimateStyle">
<slot :item="item" :index="index"></slot>
</view>
</view>
<view class="list-item" v-for="(item,index) in items"
:style="(index == curIndex && itemSelectBig) ? itemParentSelectStyle : itemParentNomalStyle"
@click="onItemClick(index)" :key="index">
<view :class="{'item-checked ':itemSelectBig && (index == curIndex)}" :style="itemAnimateStyle">
<slot :item="item" :index="index"></slot>
</view>
</view>
<view class="list-item" v-for="(item,index) in prefixItems" :key="index + 'right'"
:style="(items.length + index == curIndex && itemSelectBig) ? itemParentSelectStyle : itemParentNomalStyle"
@click="onItemClick(items.length,index)">
<view :class="{'item-checked ':itemSelectBig && (curIndex == index + items.length)}"
:style="itemAnimateStyle">
<slot :item="item" :index="index"></slot>
</view>
</view>
</view>
</view>
</template>
<script>
let lottrypoolCount = 30;
export default {
props: {
aTime: {
type: [Number],
default: 0.2
},
//每个元素的宽度 单位px
itemW: {
type: [Number],
default: uni.upx2px(170)
},
//每个元素的高度, 单位px
itemH: {
type: [Number],
default: uni.upx2px(220)
},
//两边各多出几个item
excessCount: {
type: [Number],
default: 4
},
items: {
type: [Array],
default: []
},
//选中的item 是否有放大效果
itemSelectBig: {
type: [Boolean],
default: false
},
//是否禁止触摸滑动
disableTouch: {
type: [Boolean],
default: false
},
//是否是抽奖
isLottry: {
type: [Boolean],
default: false
}
},
data() {
return {
scrolloffset: 0,
aniTime: this.aTime,
curIndex: 0,
isTouch: false,
isMove: false,
startX: 0,
endX: 0,
prizePoolItems: [], //奖池数据
}
},
computed: {
// 初始偏移量
startOffset() {
let itemScle = 1;
if (this.itemSelectBig) {
itemScle = 1.4
}
return uni.upx2px(375) - this.itemW * itemScle / 2 - this.excessCount * this.itemW;
},
scrollOffsetStyle() {
// 注意translateX 的单位得用px不然会有误差不知道什么原因不信你可以试试。把所有单位换成rpx
return `transition: all ${ this.isTouch ? 0 :this.aniTime }s ${this.isLottry?'ease-in-out':'linear'};transform: translateX(${this.scrolloffset}px);`
},
//后缀部分
prefixItems() {
return this.items.slice(0, this.excessCount);
},
//前缀部分
suffixItems() {
return this.items.slice(this.items.length - this.excessCount, this.items.length);
},
//选中的item容器放大1.4
itemParentSelectStyle() {
return `width:${this.itemW * 1.4}px;height:${this.itemH*1.4}px;transition: all ${this.aniTime}s linear;`
},
//正常情况下的容器
itemParentNomalStyle() {
return `width:${this.itemW}px;height:${this.itemH}px;transition: all ${this.aniTime}s linear;`
},
itemAnimateStyle() {
return `transition: all ${this.aniTime}s linear;transform-origin: bottom center;`
}
},
mounted() {
if (this.isLottry) {
this.initLottryData();
} else {
this.scrolloffset = this.startOffset;
}
},
methods: {
onTouchStart(e) {
if (this.disableTouch) return
let x = e.changedTouches[0].clientX;
//console.log("触摸开始=====",x);
this.isTouch = true;
// 记录之前的偏移量
this.oldOffset = this.scrolloffset;
this.startX = x;
},
onTouchMove(e) {
if (this.disableTouch) return
this.isMove = true;
let x = e.changedTouches[0].clientX;
console.log("触摸结束=====", x);
this.endX = x;
this.scrolloffset = this.oldOffset + this.endX - this.startX;
},
onTouchEnd(e) {
if (this.disableTouch || this.isLottry) return
let x = e.changedTouches[0].clientX;
//console.log("触摸结束=====",x);
if (this.isMove && !this.isLottry) {
//只有移动的时候才会对不齐中间点击item的时候不执行这个
this.moveItemAfterToCenter();
}
this.isTouch = false;
this.isMove = false;
},
//针对触摸后屏幕中心点没有和item的中心对齐使其对齐
moveItemAfterToCenter() {
//1、计算出当前偏移量在哪个item上求curIndex
//this.scrolloffset = startOffset - realIndex * this.itemW;
//根据上面的公式计算realIndex = (startOffset - this.scrolloffset)/this.itemW;
//因为偏移量是指向中心位置的所以这个realIndex是中间位置也就是 realIndex ± 0.5 都是这个item,所以必须是四舍五入,要不然就要再减去 this.itemW/2
//最后公式是realIndex = (startOffset - this.scrolloffset - this.itemW/2)/this.itemW;
//四舍五入的情况是 let curIndex = Math.round((startOffset - this.scrolloffset) / this.itemW);
//下面是向上取整
let curIndex = Math.ceil((this.startOffset - this.scrolloffset - this.itemW / 2) / this.itemW);
this.curIndex = curIndex;
console.log("计算得到当前索引:", curIndex);
this.scrolloffset = this.startOffset - curIndex * this.itemW;
this.loopOffset(curIndex);
},
onItemClick(startIndex, index) {
if (this.disableTouch) {
return;
}
let realIndex = startIndex;
if (startIndex > 9) {
realIndex = startIndex + index;
}
if (startIndex < 0) {
realIndex = startIndex + index;
}
this.curIndex = realIndex;
this.scrolloffset = this.startOffset - realIndex * this.itemW;
//console.log("偏移量:",this.scrolloffset);
this.loopOffset(realIndex);
},
//无限循环的关键
loopOffset(realIndex) {
if (realIndex > this.items.length - 1 || realIndex < 0) {
let rindex = 0;
if (realIndex > this.items.length - 1) { //后缀item 需要回到真正的偏移量
rindex = realIndex - this.items.length;
} else { //前缀item偏移量需要指向最后边的那几个item
rindex = this.items.length + realIndex;
}
setTimeout(() => {
console.log("归正:");
//点击后有个动画时间,过了动画后以不可察觉的无动画方式使偏离量回正。
this.aniTime = 0;
this.scrolloffset = this.startOffset - rindex * this.itemW;
this.curIndex = rindex;
setTimeout(() => {
console.log("归正结束");
this.aniTime = this.aTime;
}, 100) //执行上一句需要时间,等待一会儿再把动画时间复原
}, 300)
} else {
this.curIndex = realIndex;
}
},
//设置数据
initLottryData() {
// 随机一个数作为奖品(应该注意的是,抽奖前奖品是什么就已经注定,滚动只是一个效果,奖品是随机也确实没啥大问题。))
// let winPrizeItemIndex = parseInt(Math.random()*this.items.length);
let winPrizeItemIndex = 4;
let prizeItem = this.items[winPrizeItemIndex]; //当前奖品
let listLength = this.items.length;
let prizePoolItems = [];
if (this.prizePoolItems.length == 0) { //第一次,不用考虑当前屏幕显示的是哪几个奖品
console.log("第一次抽奖:");
while (prizePoolItems.length < lottrypoolCount) {
if (prizePoolItems.length == lottrypoolCount - 3) {
//将本次奖品放入奖品池中
prizePoolItems.push(prizeItem);
} else {
//从商品池中随机选一个
const randomIndex = parseInt(Math.random() * listLength);
prizePoolItems.push(this.items[randomIndex]);
}
}
} else {
//需要上一次抽奖的后三个,放在这次奖品池数据的头三个,形成视觉误差,
prizePoolItems.push(this.prizePoolItems[this.prizePoolItems.length - 5]);
prizePoolItems.push(this.prizePoolItems[this.prizePoolItems.length - 4]);
prizePoolItems.push(this.prizePoolItems[this.prizePoolItems.length - 3]);
prizePoolItems.push(this.prizePoolItems[this.prizePoolItems.length - 2]);
prizePoolItems.push(this.prizePoolItems[this.prizePoolItems.length - 1]);
while (prizePoolItems.length < lottrypoolCount) {
if (prizePoolItems.length == lottrypoolCount - 3) {
//将本次奖品放入奖品池中
prizePoolItems.push(prizeItem);
} else {
//从商品池中随机选一个
const randomIndex = parseInt(Math.random() * listLength);
prizePoolItems.push(this.items[randomIndex]);
}
}
}
//记录上一次的商品池进神
this.prizePoolItems = prizePoolItems;
this.items = prizePoolItems;
console.log("奖品池数据:", prizePoolItems);
this.aniTime = 0;
this.scrolloffset = this.startOffset - 2 * this.itemW;
setTimeout(() => {
this.aniTime = this.aTime;
}, 100)
},
startDraw() {
console.log("startDraw");
if (this.drawTimer) {
uni.showToast({
title: "正在抽奖,请稍后。。。",
icon: "none"
})
return
}
this.scrolloffset = this.startOffset - 27 * this.itemW;
console.log("this.scrolloffset", this.scrolloffset);
this.drawTimer = setTimeout(() => {
clearTimeout(this.drawTimer);
this.drawTimer = null;
this.initLottryData();
}, this.aniTime * 1000);
}
}
}
</script>
<style lang="scss">
.scroll-view {
position: relative;
height: 152.78rpx;
overflow: hidden;
display: flex;
align-items: center;
flex-direction: row;
box-sizing: border-box;
}
.view-list {
position: relative;
display: flex;
flex-wrap: nowrap;
flex-direction: row;
align-items: center;
box-sizing: border-box;
.list-item {
position: relative;
display: flex;
justify-content: center;
align-items: center;
box-sizing: border-box;
transform-origin: bottom center;
// .item—normal {
// box-sizing: border-box;
// transform-origin: bottom center;
// }
.item-checked {
background-color: gold;
transform: scale(1.4);
}
.item-center-line {
position: absolute;
height: 100%;
width: 1rpx;
background-color: green;
box-sizing: border-box;
}
}
}
</style>