有许多第三方抽奖的转盘的库,如果对样式没有要求的,建议直接用第三方库,
这是我看到比较好的转盘库:在 Vue 中使用 | 基于 Js / TS / Vue / React / 微信小程序 / uni-app / Taro 的【大转盘 & 九宫格 & 老虎机】抽奖插件
但是转盘有样式要求的,可以把下面手写转盘参考,这可以设置你想要的装盘样式,旋转停止在你想要停的位置,当然也可以设置随机旋转停止位置
支持自动设置转盘颜色,文字颜色,装盘伞形块个数
一、先看具体实现效果:
二,具体实现
1、上面展示的转盘仅用到的三张图片
当然,没有类似图片资源一样正常使用,就是美观度不够,下面我展示是不用任何图片资源的写法
不用图片资源的效果如下:
看完下面代码,你可以根据自己想要的样式需求任意调整css
2.转盘的元素分布分析图
3.三角形的位置计算公式如下
javascript
const triangleComputeFn = () => {
// 计算每个板块三角形的边长 以第一个三角区域为例
/*
.wheel-rotate { // 正方形滚动盘父盒子元素
width: 152px; //滚动盘的宽(自己设定)
height: 152px; //滚动盘的高
border-radius: 50%;
position: relative;
}
.wheel-area1 {
position: absolute;
top: 0;
width: 0;
height: 0;
/ 代表除法
// 定位在 滚动盘left举例 公式:left = 滚动盘的宽/2 - R(R为border-right的大小)
left: 25.24px;
//border-right,border-left的大小 公式为:R=滚动盘的宽*4 / 轮盘分割的块数 / 2
//border-top的大小 公式为:L=滚动盘的宽*4 / 轮盘分割的块数 / 2
border-right: 50.66px solid transparent;
border-left: 50.66px solid transparent;
border-top: 76px solid #ea3033;
transform-origin: bottom; //以底部中心为圆心
transform: rotate(60deg); // 旋转角度的公式 : N = 360 / 轮盘分割的块数
} */
}
4、最终的实现完整代码和逻辑如下 (这里是放了一个转盘组件的完整代码):
javascript
<template>
<div class="wheel">
<div class="wheel-title">转盘</div>
<div class="wheel-container">
<!-- 转盘div区域 -->
<div class="wheel-box">
<div class="wheel-edge"></div>
<!-- <div class="wheel-center1"></div> -->
<!-- <div class="wheel-center2"></div> -->
<div class="wheel-center3"></div>
<div class="wheel-rotate" ref="myZPan">
<template v-for="item in wheelList" :key="item.id">
<div
class="wheel-area"
:style="{
borderTopColor: item.bgColor,
transform: 'rotate(' + `${item.RotateDeg}` + ')'
}"
>
<div class="wheel-text" :style="{ color: item.textColor }">{{ item.name }}</div>
</div>
</template>
<!-- <div class="wheel-area1">
<div class="wheel-text">1~1</div>
</div>
<div class="wheel-area2">
<div class="wheel-text">2~2</div>
</div>
<div class="wheel-area3">
<div class="wheel-text">3~3</div>
</div>
<div class="wheel-area4">
<div class="wheel-text">4~4</div>
</div>
<div class="wheel-area5">
<div class="wheel-text">5~5</div>
</div>
<div class="wheel-area6">
<div class="wheel-text">6~6</div>
</div> -->
</div>
</div>
</div>
<div class="wheel-col">
<div class="wheel-place">
<div class="wheel-place-text">指定停止位置:</div>
<div class="wheel-place-input">
<input
v-model="stopValue"
placeholder="输入停止值(1~6)"
maxlength="10"
min="1"
pattern="[0-9]*"
inputmode="numeric"
type="number"
class="inputSum"
@input="changeMailInput"
/>
</div>
<el-button class="wheel-button" type="primary" @click="goRewardFn">抽</el-button>
</div>
<div class="wheel-place">
<div class="wheel-place-text">随机停止位置:</div>
<el-button class="wheel-button" type="primary" @click="goRandomFn">抽</el-button>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue'
const myZPan = ref<any>(null) //需要旋转的元素dom
const endAngle = ref(0) //计算要最终旋转的弧度
const stopValue = ref(1) //停摆位置
const NumberTurns = ref(1) //旋转圈数 初始值
const changeMailInput = () => {
console.log('输入框的改变值为', stopValue.value)
}
const onRoll = (index: number) => {
// 开始旋转转盘
let angle = 0 //最终旋转的角度
NumberTurns.value = NumberTurns.value + 4 //每轮旋转的次数
//计算终止角度,加指定停止位置 这里(360 / NSum.value)的计算公式为 360 / 轮盘分割的块数
angle = 360 * NumberTurns.value + (360 - (index - 1) * (360 / NSum.value))
myZPan.value.style = 'transform:rotate(' + angle + 'deg); transition: all 3s ease;' //设置旋转角度
//在元素上应用一个 CSS 过渡(通过 transition 属性),并且那个过渡完成时,就会触发 transitionend 事件
myZPan.value.addEventListener('transitionend', stopRoll)
endAngle.value = angle
console.log('设置旋转角度', endAngle.value, index)
}
const stopRoll = () => {
// 停止旋转转盘
myZPan.value.style = 'transform:rotate(' + endAngle.value + 'deg);' //设置旋转角度
hasDraw.value = true //抽奖结束
}
const hasDraw = ref(true) //是否正在抽奖中,true是可以抽,false 是正在抽奖中
const goRewardFn = () => {
if (!hasDraw.value) return
hasDraw.value = false //开始抽奖--正在抽奖中
onRoll(stopValue.value)
// return;
}
const goRandomFn = () => {
if (!hasDraw.value) return
hasDraw.value = false //开始抽奖--正在抽奖中
// 1到6之间的随机整数
stopValue.value = Math.floor(Math.random() * 6) + 1
console.log('1到6之间的随机整数', stopValue.value)
onRoll(stopValue.value)
// return;
}
interface WheelList {
id: number | string //唯一id标识 -->必填
name: string // 伞形块名字 -->必填
bgColor: string //伞形块颜色 -->选填
textColor: string // 伞形块文字颜色 -->选填
RotateDeg: string //伞形块需要在圆盘摆放的位置 -->不用填写,下面会计算出来
}
// wheelList伞形块数组长度必须wheelList>=3,要不样式会出错
const wheelList = ref<Array<WheelList>>([
{
id: 1,
name: '1~1',
bgColor: '#ea3033',
textColor: '#fff',
RotateDeg: '0'
},
{
id: 2,
name: '2~2',
bgColor: '#33cdef',
textColor: '#003790',
RotateDeg: '60deg'
},
{
id: 3,
name: '3~3',
bgColor: '#f674f2',
textColor: '#6c0067',
RotateDeg: '120deg'
},
{
id: 4,
name: '4~4',
bgColor: '#2876f4',
textColor: '#fff',
RotateDeg: '180deg'
},
{
id: 5,
name: '5~5',
bgColor: '#86eb21',
textColor: '#215200',
RotateDeg: '240deg'
},
{
id: 6,
name: '6~6',
bgColor: '#ffc212',
textColor: '#cc0001',
RotateDeg: '300deg'
},
{
id: 7,
name: '7~7',
bgColor: '#86eb21',
textColor: '#215200',
RotateDeg: '240deg'
},
{
id: 8,
name: '8~8',
bgColor: '#f674f2',
textColor: '#6c0067',
RotateDeg: '120deg'
},
{
id: 9,
name: '9~9',
bgColor: '#2876f4',
textColor: '#fff',
RotateDeg: '180deg'
}
])
const boxWidth = ref(152) //滚动盘的宽(这里我自己设定盒子宽高152px)
const positionLeft = ref('') //三角形定位在 滚动盘left
const borderLeft = ref('') //三角形宽的距离
const borderTop = ref('') //三角形高的距离
const transformRotate = ref(0) ///三角形旋转角度 初始值为0
const NSum = ref(wheelList.value.length) //轮盘分割的块数
const triangleComputeFn = () => {
// 计算每个板块三角形的边长 以第一个三角区域为例
/*
.wheel-rotate { // 正方形滚动盘父盒子元素
width: 152px; //滚动盘的宽(自己设定)
height: 152px; //滚动盘的高
border-radius: 50%;
position: relative;
}
.wheel-area1 {
position: absolute;
top: 0;
width: 0;
height: 0;
/ 代表除法
// 定位在 滚动盘left举例 公式:left = 滚动盘的宽/2 - R(R为border-right的大小)
left: 25.24px;
//border-right,border-left的大小 公式为:R=滚动盘的宽*4 / 轮盘分割的块数 / 2
//border-top的大小 公式为:L=滚动盘的宽/2
border-right: 50.66px solid transparent;
border-left: 50.66px solid transparent;
border-top: 76px solid #ea3033;
transform-origin: bottom; //以底部中心为圆心
transform: rotate(60deg); // 旋转角度的公式 : N = 360 / 轮盘分割的块数
} */
let boxWidthValue = boxWidth.value //滚动盘的宽(自己设定)数值
let positionLeftValue = 0 //三角形定位在 滚动盘left数值
let borderLeftValue = 0 //三角形宽的距离数值
let borderTopValue = 0 //三角形高的距离数值
let transformRotateValue = 0 ///三角形旋转角度数值
let NValue = NSum.value || wheelList.value.length //轮盘分割的块数数值
borderLeftValue = Number(((boxWidthValue * 4) / NValue / 2).toFixed(2))
positionLeftValue = boxWidthValue / 2 - borderLeftValue
borderTopValue = boxWidthValue / 2
transformRotateValue = 360 / NValue
positionLeft.value = positionLeftValue + 'px'
borderLeft.value = borderLeftValue + 'px'
borderTop.value = borderTopValue + 'px'
transformRotate.value = transformRotateValue
const newWheelList = wheelList.value.map((item, index) => {
const ele = Object.assign(item, {
RotateDeg: transformRotateValue * index + 'deg'
})
return ele
})
wheelList.value = newWheelList
// console.log('newWheelList', newWheelList)
}
onMounted(() => {
triangleComputeFn()
})
</script>
<style scoped long="scss">
.wheel {
width: 200px;
height: 300px;
border: 2px solid #504d4d;
border-radius: 5px;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
.wheel-title {
width: 100%;
height: 30px;
font-size: 16px;
display: flex;
justify-content: center;
align-items: center;
background-color: rgb(226, 241, 157);
}
.wheel-container {
width: 200px;
height: 200px;
display: flex;
justify-content: center;
align-items: center;
.wheel-box {
width: 180px;
height: 180px;
background-color: antiquewhite;
border-radius: 50%;
position: relative;
display: flex;
justify-content: center;
align-items: center;
.wheel-edge {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
z-index: 2;
width: 180px;
height: 180px;
border-radius: 50%;
background-size: contain;
background-position: center center;
background-repeat: no-repeat;
/* background-image: url('@/views/learning/img/wheel/zpp.png'); */
}
.wheel-center1 {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
z-index: 3;
width: 30px;
height: 30px;
border-radius: 50%;
background-size: contain;
background-position: center center;
background-repeat: no-repeat;
/* background-image: url('@/views/learning/img/wheel/zp1.png'); */
}
.wheel-center2 {
position: absolute;
top: 68px;
left: 77px;
z-index: 4;
width: 30px;
height: 30px;
border-radius: 50%;
background-size: contain;
background-position: center center;
background-repeat: no-repeat;
/* background-image: url('@/views/learning/img/wheel/zp-zj.png'); */
transform: rotate(180deg);
}
.wheel-center3 {
position: absolute;
top: 66px;
left: 81px;
width: 0;
height: 0;
z-index: 5;
border-radius: 50%;
border-right: 10px solid transparent;
border-left: 10px solid transparent;
border-bottom: 30px solid #ceea30;
}
.wheel-rotate {
width: 152px;
height: 152px;
border-radius: 50%;
overflow: hidden;
/* background-color: rgb(112, 110, 107); */
position: relative;
.wheel-area {
position: absolute;
top: 0;
left: v-bind(positionLeft);
width: 0;
height: 0;
z-index: 1;
border-right: v-bind(borderLeft) solid transparent;
border-left: v-bind(borderLeft) solid transparent;
border-top: v-bind(borderTop) solid #ea3033;
display: flex;
align-items: center;
justify-content: center;
transform-origin: bottom;
transform: rotate(0deg);
.wheel-text {
position: absolute;
bottom: 20px;
left: 50%;
font-size: 12px;
color: #fff;
font-weight: bold;
transform-origin: left center;
transform: rotate(270deg);
text-align: left;
}
}
.wheel-area1 {
position: absolute;
top: 0;
left: 25.24px;
width: 0;
height: 0;
z-index: 1;
border-right: 50.66px solid transparent;
border-left: 50.66px solid transparent;
border-top: 76px solid #ea3033;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
/* transform-origin: bottom;
transform: rotate(60deg); */
.wheel-text {
position: absolute;
bottom: 20px;
left: 50%;
font-size: 12px;
color: #fff;
font-weight: bold;
transform-origin: left center;
transform: rotate(270deg);
text-align: left;
}
}
.wheel-area2 {
position: absolute;
top: 0;
left: 25.24px;
width: 0;
height: 0;
z-index: 1;
border-right: 50.66px solid transparent;
border-left: 50.66px solid transparent;
border-top: 76px solid #33cdef;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transform-origin: bottom;
transform: rotate(60deg);
.wheel-text {
position: absolute;
bottom: 20px;
left: 50%;
font-size: 12px;
color: #003790;
font-weight: bold;
transform-origin: left center;
transform: rotate(270deg);
text-align: left;
}
}
.wheel-area3 {
position: absolute;
top: 0;
left: 25.24px;
width: 0;
height: 0;
z-index: 1;
border-right: 50.66px solid transparent;
border-left: 50.66px solid transparent;
border-top: 76px solid #f674f2;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transform-origin: bottom;
transform: rotate(120deg);
.wheel-text {
position: absolute;
bottom: 20px;
left: 50%;
font-size: 12px;
color: #6c0067;
font-weight: bold;
transform-origin: left center;
transform: rotate(270deg);
text-align: left;
}
}
.wheel-area4 {
position: absolute;
top: 0;
left: 25.24px;
width: 0;
height: 0;
z-index: 1;
border-right: 50.66px solid transparent;
border-left: 50.66px solid transparent;
border-top: 76px solid #2876f4;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transform-origin: bottom;
transform: rotate(180deg);
.wheel-text {
position: absolute;
bottom: 20px;
left: 50%;
font-size: 12px;
color: #fff;
font-weight: bold;
transform-origin: left center;
transform: rotate(270deg);
text-align: left;
}
}
.wheel-area5 {
position: absolute;
top: 0;
left: 25.24px;
width: 0;
height: 0;
z-index: 1;
border-right: 50.66px solid transparent;
border-left: 50.66px solid transparent;
border-top: 76px solid #86eb21;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transform-origin: bottom;
transform: rotate(240deg);
.wheel-text {
position: absolute;
bottom: 20px;
left: 50%;
font-size: 12px;
color: #215200;
font-weight: bold;
transform-origin: left center;
transform: rotate(270deg);
text-align: left;
}
}
.wheel-area6 {
position: absolute;
top: 0;
left: 25.24px;
width: 0;
height: 0;
z-index: 1;
border-right: 50.66px solid transparent;
border-left: 50.66px solid transparent;
border-top: 76px solid #ffc212;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transform-origin: bottom;
transform: rotate(300deg);
.wheel-text {
position: absolute;
bottom: 20px;
left: 50%;
font-size: 12px;
color: #cc0001;
font-weight: bold;
transform-origin: left center;
transform: rotate(270deg);
text-align: left;
}
}
}
}
}
.wheel-col {
width: 100%;
height: 70px;
font-size: 16px;
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
background-color: rgb(226, 241, 157);
.wheel-place {
width: 100%;
height: 30px;
display: flex;
justify-content: center;
align-items: center;
.wheel-place-text {
width: 84px;
height: 30px;
font-size: 12px;
text-align: center;
line-height: 30px;
/* scale: 0.85; */
}
.wheel-place-input {
width: 60px;
height: 30px;
display: flex;
justify-content: center;
align-items: center;
.inputSum {
width: 50px;
height: 20px;
font-size: 12px;
overflow: hidden;
padding: 0;
}
}
.wheel-button {
width: 40px;
height: 20px;
font-size: 12px;
line-height: 20px;
}
}
}
}
</style>