介绍
使用支付宝原有的大转盘营销组件进行改造的,由于背景使用的图片,目前只支持 6 个奖品,一般情况下的大转盘都是这个规格。
转盘停止:之前使用的是计算角度来完成的,没有那种缓慢停止的动画。现在加了一个缓慢停止的动画,让抽奖变得更加顺滑。
录出来的动图可能会看到转盘往相反方向转动,但是真机的视觉效果上是看不出来的。
转盘效果图
页面代码
index.axml
<view class="lucky-draw-wrapper">
<view class="chance-logs-container">
<view class="remaining-chance">
您有
<text class="chance">{{ chance }}</text>
次抽奖机会
</view>
</view>
<turntable
prizeList="{{ prizeList }}"
rotTimes="{{ chance }}"
onStart="onStart"
onFinish="onFinish"
onTimesUp="onTimesUp"
/>
</view>
index.js
Page({
data: {
prizeList: [
{
prizeId: '646739d6dcca4d1f505cb0d3',
name: 'H&M100元优惠券',
type: 'COUPON',
image: 'https://gw.alipayobjects.com/zos/rmsportal/nIQUKeYBbJWliGJVhVmx.png',
},
{
prizeId: '646739d6dcca4d1f505cb0d4',
name: '2元话费券',
type: 'COUPON',
image: 'https://gw.alipayobjects.com/zos/rmsportal/HkrVjjjuxZPUMCUbPazb.png',
},
{
prizeId: '646739d6dcca4d1f505cb0d5',
name: '45元飞猪出行券',
type: 'COUPON',
image: 'https://gw.alipayobjects.com/zos/rmsportal/cDctUxwBLPCszQHRapYV.png',
},
{
prizeId: '646739d6dcca4d1f505cb0d6',
name: 'H&M10元优惠券',
type: 'COUPON',
image: 'https://gw.alipayobjects.com/zos/rmsportal/FAmIWZAWpUwlRFKqQDLz.png',
},
{
prizeId: '646739d6dcca4d1f505cb0d7',
name: '2元流量券',
type: 'COUPON',
image: 'https://gw.alipayobjects.com/zos/rmsportal/cuGomeXzMyeeZMjvVjBj.png',
},
{
prizeId: '646739d6dcca4d1f505cb0d8',
name: '谢谢参与',
type: 'NONE',
image: 'https://zos.alipayobjects.com/rmsportal/dwhgPyWAcXuvJAWlSSgU.png',
},
],
chance: 3,
},
onLoad() {
},
async onStart() {
return this.data.prizeList[Math.floor(Math.random() * 6)];
},
async onFinish(result) {
if (!result) {
return;
}
const { type, name } = result;
my.showToast({
type: 'none',
content: type === 'NONE' ? '很遗憾,差点就中奖了' : `恭喜您,获得${name}`,
});
this.setData({
chance: this.data.chance - 1,
});
},
onTimesUp() {
my.showToast({
type: 'none',
content: '您的抽奖次数已用完',
});
},
});
index.json
{
"transparentTitle": "auto",
"titlePenetrate":"YES",
"barButtonTheme": "default",
"usingComponents": {
"turntable": "./components/Turntable/index"
}
}
turntable 组件代码
index.axml
<view class="turntable-container" style="width:{{ width }}rpx; height:{{ width }}rpx;">
<view
class="turntable-list {{ animationStatus }}"
style="background:url('{{ bgImg }}') 0% 0% / 100% 100% no-repeat; {{ transform }}"
>
<view a:for="{{ prizeList }}" a:for-index="i">
<view class="turntable-item">
<view
class="turntable-img"
style="width:{{ prizeWidth }}; padding-top:{{ prizePaddingTop }}; transform:rotate({{ (i + 0.5) / 6 }}turn);{{ itemTransformOrigin }}"
>
<view class="prize-name {{ item.type === 'NONE' ? 'none' : '' }}">{{ item.name }}</view>
<view a:if="{{ item.coupon }}" class="desc">{{ item.coupon.shortDesc }}</view>
<image src="{{ item.image }}" mode="widthFix" class="prize-img" />
</view>
</view>
</view>
</view>
<view class="turntable-btn" >
<image src="{{ btnImg }}" mode="widthFix" class="img" onTap="onDraw" />
</view>
</view>
index.acss
.turntable-container {
position: relative;
margin: auto;
border-radius: 50%;
overflow: hidden;
}
.turntable-list {
position: absolute;
border-radius: 50%;
width: inherit;
height: inherit;
transition: all 6s ease;
-webkit-transition: all 6s ease;
}
.turntable-list.scrolling {
animation: scroll-start 0.4s linear infinite;
}
@keyframes scroll-start {
from {
transform: rotate(0);
}
to {
transform: rotate(360deg);
}
}
.turntable-list.stop {
animation: scroll-slow-down 0.7s ease-out;
}
@keyframes scroll-slow-down {
from {
transform: rotate(0);
}
to {
transform: rotate(360deg);
}
}
.turntable-list.stop-win {
animation: scroll-slow-down-win 0.7s ease-out;
}
@keyframes scroll-slow-down-win {
from {
transform: rotate(0);
}
to {
transform: rotate(330deg);
}
}
.turntable-item {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
.turntable-img {
position: relative;
display: block;
margin: 0 auto;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
}
.turntable-img .prize-name {
width: 200rpx;
padding: 30rpx 0 20rpx;
color: #000;
font-weight: 600;
font-size: 32rpx;
line-height: 30rpx;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.turntable-img .prize-name.none {
color: rgba(17, 17, 17, 0.55);
}
.turntable-img .desc {
margin-top: -10rpx;
font-size: 20rpx;
line-height: 28rpx;
color: rgba(0, 0, 0, 0.65);
}
.turntable-img .prize-img {
display: block;
max-width: 120rpx;
height: 85rpx;
}
.turntable-btn {
position: absolute;
top: -12rpx;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.turntable-btn .img {
width: 20%;
}
index.js
Component({
data: {
degValue: 0, // 旋转角度
prizeWidth: 0, // 奖项背景图宽度计算值
prizePaddingTop: 0, // 奖项上边距计算值
itemTransformOrigin: '', // 奖项旋转原点计算值
animationStatus: '',
transform: '',
},
props: {
width: 690, // 画布大小,默认单位 rpx
initDeg: 0, // 初始旋转角度
rotTimes: 0, // 抽奖机会次数
prizeList: [], // 奖品列表
prizeWidth: NaN, // 奖项宽度
prizePaddingTop: NaN, // 奖项距离圆弧的内边距
bgImg: 'https://gw.alipayobjects.com/zos/rmsportal/YIunNQVWkFRxUTaUNhOZ.png', // 背景图
btnImg: 'https://gw.alipayobjects.com/zos/rmsportal/JHenAywYHZTLbbrnkIFN.png', // 按钮图
onStart() {}, // 开始回调
onFinish() {}, // 结束回调
onTimesUp() {}, // 次数用尽的回调
},
didMount() {
const widthNum = this._getNum(this.props.width);
const widthUnit = this._getUnit(this.props.width);
const { prizeWidth } = this.props;
const paddingTop = this.props.prizePaddingTop;
this.setData({
degValue: this.props.initDeg,
itemTransformOrigin: `transform-origin: 50% ${0.5 * widthNum}${widthUnit};`,
prizeWidth: isNaN(prizeWidth) ? this._calculatePrizeWidth() : prizeWidth,
prizePaddingTop: isNaN(paddingTop) ? this._calculatePrizePaddingTop() : paddingTop,
});
this.count = 6; // 奖品个数
this.rotNum = 0; // 当前是第几次抽奖
this.onRunning = false; // 是否正在抽奖
},
methods: {
async onDraw() {
if (this.onRunning) {
return;
}
if (this.props.rotTimes < 1) {
this.props.onTimesUp();
return;
}
this.onRunning = true;
this.setData({ animationStatus: 'scrolling' });
const result = await this.props.onStart();
// 延迟2秒钟展示奖品
setTimeout(() => {
this.done(result);
}, 2000);
},
done(result) {
if (!result) {
this.onRunning = false;
this.props.onFinish(null);
this.setData({ animationStatus: 'stop', transform: '' });
return;
}
this.setData({ animationStatus: 'stop-win', transform: 'transform: rotate(-30deg);' });
let tempData = this.props.prizeList;
for (let index = 0; index < tempData.length; index++) {
if (tempData[index].prizeId === result.prizeId) {
const itemAfterIndex = tempData.slice(index, this.props.prizeList.length);
const itemBeforeIndex = tempData.slice(0, index);
tempData = itemAfterIndex.concat(...itemBeforeIndex);
}
}
this.setData({
prizeList: tempData,
});
// 转盘停止1秒钟后展示弹框
setTimeout(() => {
this.onRunning = false;
this.props.onFinish(result);
}, 1000);
},
_getNum(str) {
// 获取像素选项数值
return parseFloat(str);
},
_getUnit(str) {
// 获取像素选项单位
// eslint-disable-next-line no-param-reassign
str += '';
return (str.match(/[a-z]+$/) || [])[0] || 'rpx';
},
_calculatePrizeWidth() {
// 等边三角形内接正方形边长: (4 - 2 * 根号3) * 边长
const widthNum = this._getNum(this.props.width);
const widthUnit = this._getUnit(this.props.width);
return (4 - 2 * Math.sqrt(3)) * 0.5 * widthNum + widthUnit;
},
_calculatePrizePaddingTop() {
// 等边三角形一边的中点离过该边两点的圆弧的距离: 边长 - 边长 * (根号3 / 2)
const widthNum = this._getNum(this.props.width);
const widthUnit = this._getUnit(this.props.width);
return 0.5 * widthNum - 0.25 * widthNum * Math.sqrt(3) + widthUnit;
},
},
});
index.json
{
"component": true
}