一、画圆盘
实现思路:
- 根据奖品数量将圆盘等分,计算出一份奖品所占的角度和大致宽度
- 将每一格奖品底边的中点设置在圆盘的原点处
- 根据顺序计算每一格奖品所需要旋转的角度,并旋转至各自的位置,铺满360度圆盘
- 将指针定位至圆盘中心处
奖品数据
const prizeList = [
{
name: "奖品1",
color: "red"
},
{
name: "奖品2",
color: "orange"
},
{
name: "奖品3",
color: "yellow"
},
{
name: "奖品4",
color: "green"
},
{
name: "奖品5",
color: "hotpink"
},
{
name: "奖品6",
color: "blue"
},
{
name: "奖品7",
color: "purple"
},
{
name: "奖品8",
color: "gray"
}
]
data() {
return {
prizeLists: [],
angleList: []
}
}
计算旋转角度
initPrizeList() {
const length = this.prizeList.length // 奖品数量
const width = parseInt(3.14 * 300 / length) // 每份奖品的大致宽度,由于抽奖指针最终只会定格在奖品的最中间位置,所以这里只需计算大致宽度即可,有重叠部分对页面视觉和结果无影响
const halfWidth = -parseInt(width / 2) // 奖品初始位置需要偏离的值
const average = 360 / length // 均分圆盘,每份奖品所占的角度
const half = average / 2 // 每份奖品的中间位置,也是指针最终停留的角度
list.forEach((item, index) => {
const angle = - (index * average + half) // 依次计算每份奖品的旋转角度,初始位置都是朝向12点方向
item.style = `transform: rotate(${angle}deg); width: ${width}px; marginLeft: ${halfWidth}px` // 首先将奖品全部移动到"下边的中点至圆盘原点"的状态,再旋转到上一步计算出来的角度
this.angleList.push(index * average + half) // 收集每个奖品中间位置的角度
})
return [...list]
}
页面结构
<div class="lottery-wrap">
<img class="lottery-pointer" :src="pointerImg" @click="beginRotate" >
<div class="prize-wrap" :style="rotateStyle">
<div class="prize-list">
<div class="prize-item" :style="item.style" v-for="(item, index) in prizeLists" :key="index">
<span class="prize-icon" :style="{backgroundColor: item.color}"></span>
<span class="prize-name">{{item.name}}</span>
</div>
</div>
</div>
</div>
.lottery-wrap {
width: 300px;
height: 300px;
border-radius: 50%;
background-color: rgba(0,0,0,0.4);
position: relative;
}
.lottery-pointer {
width: 100px;
height: 100px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 2;
}
.prize-wrap {
width: 100%;
height: 100%;
}
.prize-list {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
.prize-item {
position: absolute;
// width: 120px;
height: 150px;
box-sizing: border-box;
padding-top: 30px;
top: 0;
left: 50%;
// margin-left: -60px;
transform-origin: 50% 100%;
display: flex;
flex-direction: column;
// justify-content: center;
align-items: center;
.prize-icon {
width: 30px;
height: 30px;
}
}
}
至此圆盘的结构就完成了
二、转起来
实现思路:
- 用户点击抽奖后,只需要转动圆盘,指针保持不动即可
- 真实场景中获取后端返回的奖项后,前端只需计算出角度,再执行旋转动画即可,
- 每次计算处新角度,可以通过计算属性来让页面更新
- 需要注意的是每次旋转完要保存旋转角度,并且防止用户重复点击
初始数据
data() {
isRotating: false, // 防止重复点击旋转
rotateAngle: 0 // 当前位置
}
旋转方法
methods: {
beginRotate() {
const { isRotating, rotateAngle, angleList } = this
if (isRotating) reurn // 防止重复点击
this.isRotating = true
const angle = rotateAngle + 5 * 360 + angleList[4] - (rotateAngle % 360) // 为了视觉好看,这里默认旋转5圈以上
this.rotateAngle = angle // 保存此次旋转角度,下次旋转需要用到
setTimeout(() => {
this.isRotating = false // 本次抽奖结束,可以进行下一次
console.log("抽中了!)
}, 5000)
}
}
计算属性更新页面
computed: {
rotateStyle() {
return `
transition: transform 5000ms ease-in-out;
transform: rotate(${this.rotateAngle}deg)
`
}
}
至此完成了转盘的抽奖功能
接下来改造成可配置性更高的组件
三、改造成可配置的组件
实现思路:尽可能将可配置的数据改造成参数传入,包含背景图、指针图片、奖品列表、旋转时间、动画效果、旋转圈数等
接收参数
props: {
prizeList: {
type: Array,
default: () => []
},
pointerImg: {
type: String,
default: () => require("./img/xxx.png")
},
lotteryBgImg: {
type: String,
default: () => require("./img/xxx.png")
},
duration: {
type: Number,
default: () => 5000
},
circle: {
type: Number,
default: () => 5
},
mode: {
type: String,
default: () => "ease-in-out"
}
}
旋转方法改造
methods: {
rotating(index) {
const { isRotating, duration, circle, count, rotateAngle, angleList } = this
if (isRotating) reurn // 防止重复点击
this.isRotating = true
const angle = rotateAngle + circle * 360 + angleList[index] - (rotateAngle % 360) // 为了视觉好看,这里默认旋转5圈以上
this.rotateAngle = angle // 保存此次旋转角度,下次旋转需要用到
setTimeout(() => {
this.isRotating = false // 本次抽奖结束,可以进行下一次
console.log("抽中了!)
}, duration)
}
}
计算属性改造
computed: {
rotateStyle() {
return `
transition: transform ${this.duration}ms ${this.mode};
transform: rotate(${this.rotateAngle}deg)
`
}
}
实际场景中,最终获奖奖品一般由后端控制比较安全,因此这里需要改造成由父组件来控制, 而点击事件是在自组件中,所以做如下改造
<div class="lottery-wrap">
<img class="lottery-pointer" :src="pointerImg" @click="beginRotate" >
<div class="prize-wrap" :style="rotateStyle">
<div class="prize-list">
<div class="prize-item" :style="item.style" v-for="(item, index) in prizeLists" :key="index">
<span class="prize-icon" :style="{backgroundColor: item.color}"></span>
<span class="prize-name">{{item.name}}</span>
</div>
</div>
</div>
</div>
methods: {
beginRotate() {
this.$emit("beginRotate") // 让父组件来控制点击,并传入获奖结果
},
rotating(index) {
const { isRotating, duration, circle, count, rotateAngle, angleList } = this
if (isRotating) reurn // 防止重复点击
this.isRotating = true
const angle = rotateAngle + circle * 360 + angleList[index] - (rotateAngle % 360) // 为了视觉好看,这里默认旋转5圈以上
this.rotateAngle = angle // 保存此次旋转角度,下次旋转需要用到
setTimeout(() => {
this.isRotating = false // 本次抽奖结束,可以进行下一次
this.rotateOver()
}, duration)
},
rotateOver() {
this.$emit("rotateOver") // 抽奖结束,通知父组件
}
}
// 父组件
<turntable ref="lottery" :prizeList="prizeList" @beginRotate="beginRotate" @rotateOver="rotateOver"></turntable>
methods: {
beginRotate() {
// 这里调用后端接口拿到最终获奖结果
const res = .......
this.$refs.lottery.rotating(res)
},
rotateOver() {
// 完成抽奖结束的剩余步骤
}
}
至此,就完成简易功能的抽奖组件
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!