<template>
<div class="pie-wrapper" ref="pieWrapper">
<!-- 抽奖按钮 -->
<slot name="start"></slot>
<!-- canvas -->
<canvas
class="pie"
:height="diameter * canvasScal"
:width="diameter * canvasScal"
:style="style"
ref="canvas"
@transitionend="handleEnd"
></canvas>
</div>
</template>
<script>
export default {
name: 'LuckyPie',
data () {
return {
rotate: 0,
rotating: false,
transition: false,
// 直径(宽度)
diameter: 0,
// canvas提高清晰度放大倍数
canvasScal: 3
}
},
computed: {
style () {
const duration = this.plateConfig.duration || 4000
let style = `transform: rotate(${this.rotate}deg);`
if (this.transition) {
style += `transition-duration: ${duration}ms;`
style += `transition-property: transform;`
}
return style
},
luckyLength () {
return this.datas.length
},
sliceWidth () {
return 360 / this.luckyLength
},
radius () {
if (!this.diameter) return 0
return this.diameter / 2
},
grideAngle () {
return (Math.PI / this.luckyLength) * 2
}
},
props: {
// 转盘基础配置
plateConfig: {
type: Object,
default: () => {
return {
isOffset: 1,
// 转动圈数
rotateNumber: 5,
duration: 4000
}
}
},
// 奖项列表
datas: {
type: Array,
default () {
return []
}
},
target: {
type: [Number, String],
default: -1
}
},
watch: {
target (newVal) {
if (newVal > -1) this.start()
},
datas: {
handler (n) {
this.drawImage(n)
},
deep: true
}
},
mounted () {
this.diameter = this.$refs.pieWrapper.clientWidth
this.drawImage(this.datas)
},
methods: {
handleEnd () {
this.rotating = false
this.$emit('roll-end')
},
start () {
this.$nextTick(() => {
if (!this.rotating && this.target >= 0) {
// 重复圈数
const rotateNumber = this.plateConfig.rotateNumber || 0
this.rotating = true
this.transition = true
const index = this.datas.findIndex(item => item.position === this.target)
// 转到对应位置的偏移度
let rotate = 360 - this.sliceWidth * index
if (this.plateConfig.isOffset) { // 指针指到线上,需回退一般单个角度
rotate = Math.floor(rotate - this.sliceWidth / 2)
}
// 随机偏移度(在单个奖项内的偏移)
const range = Math.floor(this.sliceWidth / 3 * 2)
let randomOffset = -range + Math.round(Math.random() * range * 2)
// 旋转总角度
this.rotate = rotateNumber * 360 + randomOffset + rotate
}
})
},
drawImage (datas) {
if (!this.diameter) return
let singleWidth = Math.floor(Math.sin(this.grideAngle / 2) * this.radius)
const ctx = this.$refs['canvas'].getContext('2d')
const that = this
datas.map((item, index) => {
;(arg => {
const img = new Image()
img.src = datas[arg].url
img.onload = () => {
ctx.save()
ctx.translate(that.radius * this.canvasScal, that.radius * this.canvasScal)
if (this.plateConfig.isOffset) { // 偏移,指针指向线上
ctx.rotate((arg * 2 * Math.PI) / datas.length + this.grideAngle / 2)
} else { // 偏移,指针指向中间
ctx.rotate((arg * 2 * Math.PI) / datas.length)
}
ctx.drawImage(
img, -singleWidth * this.canvasScal, -that.radius * this.canvasScal,
singleWidth * 2 * this.canvasScal, that.radius * this.canvasScal
)
ctx.restore()
}
})(index)
})
}
}
}
</script>
<style lang="scss" scoped>
.pie-wrapper {
position: relative;
width: 100%;
height: 100%;
.pie {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
z-index: 1;
transform: rotate(0deg);
}
}
</style>
使用说明
<div class="plate-box">
<act-lucky-pie class="pie-use" v-if="finishPlateConfig" :datas="prizeImgArr" :target="prizePosition" :plateConfig="plateConfig"
@rollEnd="rollEnd">
<template #start>
<div class="lucky-pointer" @click="handleDraw"></div>
</template>
</act-lucky-pie>
</div>
export default {
components: { ActLuckyPie },
data() {
return {,
prizeImgArr: [],
plateConfig: {
isOffset: 1,
rotateNumber: 5,
// 转动位置
prizePosition: -1,
// 转盘基础设置
finishPlateConfig: false
}
};
},
methods: {
rollEnd() {
// 转盘转动动画结束,显示弹窗应在此设置,建议延迟500ms显示
},
handleDraw() {
// 抽奖请求
}
}
}