实际效果
一些变量
const config = {
wheel: '/image/wheel.png', // 转盘图片
icon: '/image/icon.png', // 中心 LOGO
arrow: '/image/arrow.png', // 顶部剪头
round_time: 5000, // 获取到奖品后转盘旋转动画时间
text_color: '#000', // 文字颜色
// 奖品列表
prizeList: [
{ id: 1, text: '1华为P40', img: '/image/prize1.png' },
{ id: 2, text: '2谢谢参与', img: '/image/prize2.png' },
{ id: 3, text: '3华为P40', img: '/image/prize1.png' },
{ id: 4, text: '4谢谢参与', img: '/image/prize2.png' },
{ id: 5, text: '5华为P40', img: '/image/prize1.png' },
{ id: 6, text: '6谢谢参与', img: '/image/prize2.png' },
{ id: 7, text: '7华为P40', img: '/image/prize1.png' },
{ id: 8, text: '8谢谢参与', img: '/image/prize2.png' },
]
};
HTML
<div class="content">
<div class="wheel">
<div class="round" :class="{'not_roting' : roting}"
:style="{ transition: `${config.round_time}ms`, transform: `rotate(${deg}deg)` }">
<!-- 转盘图片 -->
<img :src="config.wheel" alt="" />
<div class="prize">
<!-- 奖品,宽高均意志,重叠在一起,以转盘中心为原点旋转 360/奖品个数 的角度 -->
<!-- 这边使用了 v-for, react 使用 map, 原生或 jQuery 需要在 js 中遍历创建完 DOM 元素再插入 -->
<div v-for="(item, index) in config.prizeList" class="item_box" :key="index.toString()"
:style="{color: config.text_color, transform: `rotate(${(360 / config.prizeList.length) * index}deg)`}">
<div class="item">
<span class="text">{{ item.text }}</span>
<img class="img" :src="item.img" :alt="item.text" />
</div>
</div>
</div>
</div>
<!-- 中部LOGO -->
<div class="icon">
<img :src="config.icon" alt="" />
</div>
<div class="" arrow>
<img :src="config.arrow" alt="" />
</div>
</div>
<div class="btn">
<el-button size="large" type="primary" v-loading="loading" @click="clickBtn">
立即抽奖
</el-button>
</div>
</div>
CSS
.content {
width: 500px; /* 移动端,限制最大宽度 */
max-width: 100%;
margin: auto;
overflow-x: hidden;
.wheel {
position: relative;
width: 90%;
/* margin、padding的百分比基于宽度,在使用rem的情况下,高度和宽度保持比例可以用padding来撑起 */
padding-top: 90%;
margin: calc(100 / 750 * 100%) auto auto;
overflow: hidden;
.round {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
/* 抽完奖后复位时,旋转不需要过渡时间 */
&.not_roting { transition: 0s !important; }
.prize {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
.item_box {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
.item {
position: absolute;
top: calc(85 / 675 * 100%);
left: calc((1 - 145 / 675) * 100% * 0.5);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: calc(145 / 675 * 100%);
.text {
font-size: calc(100vw * 25 / 750);
font-weight: bold;
line-height: 1;
@media (min-width: 500px) { font-size: 17px; }
}
.img { width: calc(90 / 145 * 100%); margin-top: calc(30 / 145 * 100%); }
}
}
}
}
/* LOGO 居中 */
.icon { position: absolute; top: 50%; left: 50%; width: calc(155 / 675 * 100%); transform: translate(-50%, -50%); }
/* 箭头顶部居中 */
.arrow { position: absolute; top: calc(28 / 675 * 100%); left: 50%; width: calc(55 / 675 * 100%); transform: translateX(-50%); }
}
.btn {
width: 100%;
margin-top: calc(80 / 750 * 100%);
>button { display: block; width: calc(650 / 750 * 100%); margin: auto; }
}
}
JavaScript
// 开始抽奖
clickBtn() {
if (this.roting || this.loading) return
this.loading = true
// 前端生成随机数奖品
setTimeout(() => {
const config = this.config
const activePrize = config.prizeList[Math.floor(Math.random() * 7)]
// 使用 some 循环,获取到奖品即停止遍历
config.prizeList.some((item, index) => {
if (item.id === activePrize.id) {
// 获取该奖品的一个随机角度,具体见下方函数注释
const newDeg = this.getRote(index, config.prizeList.length)
// roting 为 true 时,转盘有旋转动画,时间为 config.round_time
this.roting = true
this.deg = newDeg
setTimeout(() => {
// 经过 config.round_time 的时间后,转盘进行复位,方便下一次抽取,deg 不取 0 而是 NewDeg % 360 以确保用户看不到转盘闪动
this.roting = false
this.deg = NewDeg % 360
}, config.round_time)
return true
}
})
this.loading = false
}, 1000);
},
/**
* 根据奖品获取转盘角度
* @param {number} index 获得奖品在 prizeList 中的 index
* @param {number} count 奖品数量
* @return {number}
*/
getRote(index, count) {
if (!count) count = 8; // 若不传奖品数量,则默认为8
const MAX_ROUND = 10; // 最大转圈数
const MIN_ROUND = 8; // 最小转圈数
const OFFSET_MULTIPLE = 0.6; // 偏移量倍数,范围 [0, 1),例如4个奖品时,偏移量是[-45, 45),若倍数为 1,则可能出现贴边的情况
const unit = 360 / count; // 单元角度,为360 ÷ 奖品总数
const offset = Math.floor(Math.random() * -unit) + unit / 2;
const newDeg = 360 * Math.floor(Math.random() * (MAX_ROUND - MIN_ROUND + 1) + MIN_ROUND) - unit * index;
return newDeg + OFFSET_MULTIPLE * offset;
};
源码
https://gitee.com/Blade_gen/vite_vue_base/blob/master/src/views/LuckDraw/index.vue