[Vue 3] Component migrated
Closed this issue · 4 comments
dathacky commented
<template>
<div class="fw-container">
<!-- wheel -->
<div
class="fw-wheel"
:style="rotateStyle"
@transitionend="onRotateEnd"
@webkitTransitionend="onRotateEnd"
>
<canvas
v-if="type === 'canvas'"
ref="wheel"
:width="canvasConfig.radius * 2"
:height="canvasConfig.radius * 2"
></canvas>
<slot name="wheel" v-else></slot>
</div>
<!-- button -->
<div class="fw-btn">
<div
v-if="type === 'canvas'"
class="fw-btn__btn"
:style="{ width: canvasConfig.btnWidth + 'px', height: canvasConfig.btnWidth + 'px' }"
@click="handleClick"
>
{{ canvasConfig.btnText }}
</div>
<div v-else class="fw-btn__image" @click="handleClick">
<slot name="button"></slot>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch, onMounted, toRef } from 'vue'
import sumBy from 'lodash/sumBy'
import random from 'lodash/random'
const canvasDefaultConfig = {
radius: 250,
textRadius: 190,
textLength: 6,
textDirection: 'horizontal',
lineHeight: 20,
borderWidth: 0,
borderColor: 'transparent',
btnText: 'GO',
btnWidth: 140,
fontSize: 34
}
const props = defineProps({
type: {
type: String,
default: 'canvas' // canvas || image
},
useWeight: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
verify: {
type: Boolean,
default: false
},
canvas: {
type: Object,
default: () => ({
radius: 250,
textRadius: 190,
textLength: 6,
textDirection: 'horizontal',
lineHeight: 20,
borderWidth: 0,
borderColor: 'transparent',
btnText: 'GO',
btnWidth: 140,
fontSize: 34
})
},
duration: {
type: Number,
default: 6000
},
timingFun: {
type: String,
default: 'cubic-bezier(0.36, 0.95, 0.64, 1)'
},
angleBase: {
type: Number,
default: 10
},
prizeId: {
type: Number,
default: 0
},
prizes: {
type: Array,
required: true,
default: () => []
}
})
const emits = defineEmits(['rotateStart', 'rotateEnd'])
const prizeId = toRef(props, 'prizeId')
const wheel = ref(null)
const isRotating = ref(false)
const rotateEndDeg = ref(false)
const prizeRes = ref({})
const canvasConfig = computed(() => Object.assign(canvasDefaultConfig, props.canvas))
const probabilityTotal = computed(() => {
if (props.useWeight) return 100
return sumBy(props.prizes, (row) => row.probability || 0)
})
const prizesIdArr = computed(() => {
const idArr = []
props.prizes.forEach((row) => {
const count = props.useWeight ? row.weight || 0 : (row.probability || 0) * decimalSpaces.value
const arr = new Array(count).fill(row.id)
idArr.push(...arr)
})
return idArr
})
const decimalSpaces = computed(() => {
if (props.useWeight) return 0
const sortArr = [...props.prizes].sort((a, b) => {
const aRes = String(a.probability).split('.')[1]
const bRes = String(b.probability).split('.')[1]
const aLen = aRes ? aRes.length : 0
const bLen = bRes ? bRes.length : 0
return bLen - aLen
})
const maxRes = String(sortArr[0].probability).split('.')[1]
const idx = maxRes ? maxRes.length : 0
return [1, 10, 100, 1000, 10000][idx > 4 ? 4 : idx]
})
const rotateStyle = computed(() => ({
'-webkit-transform': `rotateZ(${rotateEndDeg.value}deg)`,
transform: `rotateZ(${rotateEndDeg.value}deg)`,
'-webkit-transition-duration': `${rotateDuration.value}s`,
'transition-duration': `${rotateDuration.value}s`,
'-webkit-transition-timing-function:': props.timingFun,
'transition-timing-function': props.timingFun
}))
const rotateDuration = computed(() => (isRotating.value ? props.duration / 1000 : 0))
const rotateBase = computed(() => {
let angle = props.angleBase * 360
if (props.angleBase < 0) angle -= 360
return angle
})
const canRotate = computed(
() => !props.disabled && !isRotating.value && probabilityTotal.value === 100
)
function getStrArray(str, len) {
const arr = []
while (str !== '') {
let text = str.substr(0, len)
if (str.charAt(len) !== '' && str.charAt(len) !== ' ') {
const index = text.lastIndexOf(' ')
if (index !== -1) text = text.substr(0, index)
}
str = str.replace(text, '').trim()
arr.push(text)
}
return arr
}
function getTargetDeg(prizeId) {
const angle = 360 / props.prizes.length
const num = props.prizes.findIndex((row) => row.id === prizeId)
prizeRes.value = props.prizes[num]
return 360 - (angle * num + angle / 2)
}
function checkProbability() {
if (probabilityTotal.value !== 100) {
throw new Error('Prizes Is Error: Sum of probabilities is not 100!')
}
return true
}
function drawPrizeText(ctx, angle, arc, name) {
const { lineHeight, textLength, textDirection } = canvasConfig.value
const content = getStrArray(name, textLength)
if (content === null) return
textDirection === 'vertical'
? ctx.rotate(angle + arc / 2 + Math.PI)
: ctx.rotate(angle + arc / 2 + Math.PI / 2)
content.forEach((text, idx) => {
let textX = -ctx.measureText(text).width / 2
let textY = (idx + 1) * lineHeight
if (textDirection === 'vertical') {
textX = 0
textY = (idx + 1) * lineHeight - (content.length * lineHeight) / 2
}
ctx.fillText(text, textX, textY)
})
}
function drawCanvas() {
const canvasEl = wheel.value
if (canvasEl.getContext) {
const { radius, textRadius, borderWidth, borderColor, fontSize } = canvasConfig.value
const arc = Math.PI / (props.prizes.length / 2)
const ctx = canvasEl.getContext('2d')
ctx.clearRect(0, 0, radius * 2, radius * 2)
ctx.strokeStyle = borderColor
ctx.lineWidth = borderWidth * 2
ctx.font = `${fontSize}px Arial`
props.prizes.forEach((row, i) => {
const angle = i * arc - Math.PI / 2
ctx.fillStyle = row.bgColor
ctx.beginPath()
ctx.arc(radius, radius, radius - borderWidth, angle, angle + arc, false)
ctx.stroke()
ctx.arc(radius, radius, 0, angle + arc, angle, true)
ctx.fill()
ctx.save()
ctx.fillStyle = row.color
ctx.translate(
radius + Math.cos(angle + arc / 2) * textRadius,
radius + Math.sin(angle + arc / 2) * textRadius
)
drawPrizeText(ctx, angle, arc, row.name)
ctx.restore()
})
}
}
function handleClick() {
if (!canRotate.value) return
if (props.verify) {
emits('rotateStart', onRotateStart)
return
}
emits('rotateStart')
onRotateStart()
}
function onRotateStart() {
isRotating.value = true
const prizeId = props.prizeId || getRandomPrize()
rotateEndDeg.value = rotateBase.value + getTargetDeg(prizeId)
}
function onRotateEnd() {
isRotating.value = false
rotateEndDeg.value %= 360
emits('rotateEnd', prizeRes.value)
}
function getRandomPrize() {
const len = prizesIdArr.value.length
const prizeId = prizesIdArr.value[random(0, len - 1)]
return prizeId
}
onMounted(() => {
checkProbability()
if (props.type === 'canvas') drawCanvas()
})
// prizeId
watch(prizeId, (newVal) => {
if (!isRotating.value) return
let newAngle = getTargetDeg(newVal)
if (props.angleBase < 0) newAngle -= 360
const prevEndDeg = rotateEndDeg.value
let nowEndDeg = props.angleBase * 360 + newAngle
const angle = 360 * Math.floor((nowEndDeg - prevEndDeg) / 360)
if (props.angleBase >= 0) {
nowEndDeg += Math.abs(angle)
} else {
nowEndDeg += -360 - angle
}
rotateEndDeg.value = nowEndDeg
})
</script>
<style scoped lang="scss">
@import './style.scss';
</style>
dathacky commented
.fw-container {
position: relative;
display: inline-block;
font-size: 0;
overflow: hidden;
canvas,
img {
display: block;
width: 100%;
}
}
.fw-btn {
position: absolute;
top: 0;
left: 0;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
.fw-btn__btn {
position: relative;
width: 100%;
height: 100%;
background: #fff;
border: 6px solid #fff;
border-radius: 50%;
background: #15bd96;
color: #fff;
text-align: center;
font-size: 42px;
font-weight: bold;
line-height: 1;
display: flex;
justify-content: center;
align-items: center;
&:after {
content: '';
display: block;
width: 0;
height: 0;
border-left: 18px solid transparent;
border-right: 18px solid transparent;
border-bottom: 22px #fff solid;
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
}
&:before {
content: '';
display: block;
width: 0;
height: 0;
border-left: 12px solid transparent;
border-right: 12px solid transparent;
border-bottom: 18px #15bd96 solid;
position: absolute;
bottom: 100%;
left: 50%;
transform: translate(-50%) translateY(6px);
z-index: 10;
}
}
.fw-btn__image {
display: inline-block;
}
lantrinh1999 commented
<template> <div class="fw-container"> <!-- wheel --> <div class="fw-wheel" :style="rotateStyle" @transitionend="onRotateEnd" @webkitTransitionend="onRotateEnd" > <canvas v-if="type === 'canvas'" ref="wheel" :width="canvasConfig.radius * 2" :height="canvasConfig.radius * 2" ></canvas> <slot name="wheel" v-else></slot> </div> <!-- button --> <div class="fw-btn"> <div v-if="type === 'canvas'" class="fw-btn__btn" :style="{ width: canvasConfig.btnWidth + 'px', height: canvasConfig.btnWidth + 'px' }" @click="handleClick" > {{ canvasConfig.btnText }} </div> <div v-else class="fw-btn__image" @click="handleClick"> <slot name="button"></slot> </div> </div> </div> </template> <script setup> import { ref, computed, watch, onMounted, toRef } from 'vue' import sumBy from 'lodash/sumBy' import random from 'lodash/random' const canvasDefaultConfig = { radius: 250, textRadius: 190, textLength: 6, textDirection: 'horizontal', lineHeight: 20, borderWidth: 0, borderColor: 'transparent', btnText: 'GO', btnWidth: 140, fontSize: 34 } const props = defineProps({ type: { type: String, default: 'canvas' // canvas || image }, useWeight: { type: Boolean, default: false }, disabled: { type: Boolean, default: false }, verify: { type: Boolean, default: false }, canvas: { type: Object, default: () => ({ radius: 250, textRadius: 190, textLength: 6, textDirection: 'horizontal', lineHeight: 20, borderWidth: 0, borderColor: 'transparent', btnText: 'GO', btnWidth: 140, fontSize: 34 }) }, duration: { type: Number, default: 6000 }, timingFun: { type: String, default: 'cubic-bezier(0.36, 0.95, 0.64, 1)' }, angleBase: { type: Number, default: 10 }, prizeId: { type: Number, default: 0 }, prizes: { type: Array, required: true, default: () => [] } }) const emits = defineEmits(['rotateStart', 'rotateEnd']) const prizeId = toRef(props, 'prizeId') const wheel = ref(null) const isRotating = ref(false) const rotateEndDeg = ref(false) const prizeRes = ref({}) const canvasConfig = computed(() => Object.assign(canvasDefaultConfig, props.canvas)) const probabilityTotal = computed(() => { if (props.useWeight) return 100 return sumBy(props.prizes, (row) => row.probability || 0) }) const prizesIdArr = computed(() => { const idArr = [] props.prizes.forEach((row) => { const count = props.useWeight ? row.weight || 0 : (row.probability || 0) * decimalSpaces.value const arr = new Array(count).fill(row.id) idArr.push(...arr) }) return idArr }) const decimalSpaces = computed(() => { if (props.useWeight) return 0 const sortArr = [...props.prizes].sort((a, b) => { const aRes = String(a.probability).split('.')[1] const bRes = String(b.probability).split('.')[1] const aLen = aRes ? aRes.length : 0 const bLen = bRes ? bRes.length : 0 return bLen - aLen }) const maxRes = String(sortArr[0].probability).split('.')[1] const idx = maxRes ? maxRes.length : 0 return [1, 10, 100, 1000, 10000][idx > 4 ? 4 : idx] }) const rotateStyle = computed(() => ({ '-webkit-transform': `rotateZ(${rotateEndDeg.value}deg)`, transform: `rotateZ(${rotateEndDeg.value}deg)`, '-webkit-transition-duration': `${rotateDuration.value}s`, 'transition-duration': `${rotateDuration.value}s`, '-webkit-transition-timing-function:': props.timingFun, 'transition-timing-function': props.timingFun })) const rotateDuration = computed(() => (isRotating.value ? props.duration / 1000 : 0)) const rotateBase = computed(() => { let angle = props.angleBase * 360 if (props.angleBase < 0) angle -= 360 return angle }) const canRotate = computed( () => !props.disabled && !isRotating.value && probabilityTotal.value === 100 ) function getStrArray(str, len) { const arr = [] while (str !== '') { let text = str.substr(0, len) if (str.charAt(len) !== '' && str.charAt(len) !== ' ') { const index = text.lastIndexOf(' ') if (index !== -1) text = text.substr(0, index) } str = str.replace(text, '').trim() arr.push(text) } return arr } function getTargetDeg(prizeId) { const angle = 360 / props.prizes.length const num = props.prizes.findIndex((row) => row.id === prizeId) prizeRes.value = props.prizes[num] return 360 - (angle * num + angle / 2) } function checkProbability() { if (probabilityTotal.value !== 100) { throw new Error('Prizes Is Error: Sum of probabilities is not 100!') } return true } function drawPrizeText(ctx, angle, arc, name) { const { lineHeight, textLength, textDirection } = canvasConfig.value const content = getStrArray(name, textLength) if (content === null) return textDirection === 'vertical' ? ctx.rotate(angle + arc / 2 + Math.PI) : ctx.rotate(angle + arc / 2 + Math.PI / 2) content.forEach((text, idx) => { let textX = -ctx.measureText(text).width / 2 let textY = (idx + 1) * lineHeight if (textDirection === 'vertical') { textX = 0 textY = (idx + 1) * lineHeight - (content.length * lineHeight) / 2 } ctx.fillText(text, textX, textY) }) } function drawCanvas() { const canvasEl = wheel.value if (canvasEl.getContext) { const { radius, textRadius, borderWidth, borderColor, fontSize } = canvasConfig.value const arc = Math.PI / (props.prizes.length / 2) const ctx = canvasEl.getContext('2d') ctx.clearRect(0, 0, radius * 2, radius * 2) ctx.strokeStyle = borderColor ctx.lineWidth = borderWidth * 2 ctx.font = `${fontSize}px Arial` props.prizes.forEach((row, i) => { const angle = i * arc - Math.PI / 2 ctx.fillStyle = row.bgColor ctx.beginPath() ctx.arc(radius, radius, radius - borderWidth, angle, angle + arc, false) ctx.stroke() ctx.arc(radius, radius, 0, angle + arc, angle, true) ctx.fill() ctx.save() ctx.fillStyle = row.color ctx.translate( radius + Math.cos(angle + arc / 2) * textRadius, radius + Math.sin(angle + arc / 2) * textRadius ) drawPrizeText(ctx, angle, arc, row.name) ctx.restore() }) } } function handleClick() { if (!canRotate.value) return if (props.verify) { emits('rotateStart', onRotateStart) return } emits('rotateStart') onRotateStart() } function onRotateStart() { isRotating.value = true const prizeId = props.prizeId || getRandomPrize() rotateEndDeg.value = rotateBase.value + getTargetDeg(prizeId) } function onRotateEnd() { isRotating.value = false rotateEndDeg.value %= 360 emits('rotateEnd', prizeRes.value) } function getRandomPrize() { const len = prizesIdArr.value.length const prizeId = prizesIdArr.value[random(0, len - 1)] return prizeId } onMounted(() => { checkProbability() if (props.type === 'canvas') drawCanvas() }) // prizeId watch(prizeId, (newVal) => { if (!isRotating.value) return let newAngle = getTargetDeg(newVal) if (props.angleBase < 0) newAngle -= 360 const prevEndDeg = rotateEndDeg.value let nowEndDeg = props.angleBase * 360 + newAngle const angle = 360 * Math.floor((nowEndDeg - prevEndDeg) / 360) if (props.angleBase >= 0) { nowEndDeg += Math.abs(angle) } else { nowEndDeg += -360 - angle } rotateEndDeg.value = nowEndDeg }) </script> <style scoped lang="scss"> @import './style.scss'; </style>
I used your code snippet, but it's not working.
dathacky commented
I used your code snippet, but it's not working.
import style.scss ?
@lantrinh1999
XiaoLin1995 commented
Please upgrade to the latest version to support vue3.
npm install vue-fortune-wheel@latest