349 lines
10 KiB
Vue
349 lines
10 KiB
Vue
<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> |