当前位置: 首页 > wzjs >正文

做印尼购物网站如何发货塘沽网红图书馆地址

做印尼购物网站如何发货,塘沽网红图书馆地址,宝安品牌网站建设,广州公司网站托管在 Web 应用开发中,抽奖功能是提升用户参与度的常用手段。使用 Vue3 结合 canvas 技术,我们可以轻松实现一个高度自定义的抽奖转盘组件,不仅能设定中奖概率,还能灵活配置奖项图标和名称。本文将详细介绍该组件的实现原理、步骤&am…

在 Web 应用开发中,抽奖功能是提升用户参与度的常用手段。使用 Vue3 结合 canvas 技术,我们可以轻松实现一个高度自定义的抽奖转盘组件,不仅能设定中奖概率,还能灵活配置奖项图标和名称。本文将详细介绍该组件的实现原理、步骤,并提供完整代码。

实现原理

抽奖转盘组件的核心在于通过 canvas 绘制转盘图形,并结合动画效果实现转盘的旋转。通过设定不同奖项的中奖概率,随机选择中奖奖项,并使用缓动函数控制转盘旋转的动画效果,使转盘最终停在对应的中奖区域。

实现步骤

1. 定义组件模板

在 Vue3 的单文件组件(.vue)中,定义抽奖转盘组件的模板。模板包含 canvas 画布用于绘制转盘,一个中心按钮用于触发抽奖,以及一个提示框用于展示中奖结果。

<template><div class="lottery-container"><div class="wheel-container"><canvas ref="canvasRef" width="350" height="350"></canvas><div class="wheel-center" @click="startLottery"><div class="start-btn">{{ isRotating? '抽奖中...' : '开始抽奖' }}</div></div><div class="pointer"></div></div><div v-if="result" class="result-modal"><h3>恭喜您获得: {{ result.name }}</h3><button @click="result = null">确定</button></div></div></template>

2. 定义组件属性和数据

使用defineProps定义组件接收的属性,包括奖项配置(prizes)、抽奖持续时间(duration)、转盘颜色(colors)。同时,使用ref定义组件内部状态,如是否正在旋转(isRotating)、旋转角度(rotationAngle)、选中的奖项索引(selectedPrizeIndex)、中奖结果(result)等。

import { ref, onMounted, onBeforeUnmount, watch } from 'vue'const props = defineProps({prizes: {type: Array,required: true,validator: (value) => {return value.length >= 2 && value.every(item =>item.name && typeof item.probability === 'number' && item.probability >= 0)}},duration: {type: Number,default: 5000},colors: {type: Array,default: () => ['#FF5252', '#FF4081', '#E040FB', '#7C4DFF','#536DFE', '#448AFF', '#40C4FF', '#18FFFF','#64FFDA', '#69F0AE', '#B2FF59', '#EEFF41','#FFFF00', '#FFD740', '#FFAB40', '#FF6E40']}})const emit = defineEmits(['start', 'end'])const canvasRef = ref(null)const isRotating = ref(false)const rotationAngle = ref(0)const selectedPrizeIndex = ref(-1)const result = ref(null)const loadedImages = ref({})let animationFrameId = nulllet startTime = 0

3. 预加载图片

如果奖项配置中有图标,需要提前加载图片,确保绘制转盘时图标能正常显示。通过Image对象加载图片,并在onload事件中触发转盘绘制。

const loadImages = () => {props.prizes.forEach((prize, index) => {if (prize.icon) {const img = new Image()img.src = prize.iconimg.onload = () => {loadedImages.value[index] = imgdrawWheel()}img.onerror = () => {console.error(`图片加载失败: ${prize.icon}`)}}})}

4. 绘制转盘

获取 canvas 上下文,根据奖项数量计算每个扇形的角度,绘制转盘的扇形区域,并在每个扇形区域绘制奖项名称和图标。

const drawWheel = () => {const canvas = canvasRef.valueif (!canvas) returnconst ctx = canvas.getContext('2d')const centerX = canvas.width / 2const centerY = canvas.height / 2const radius = Math.min(centerX, centerY) - 10const arc = Math.PI * 2 / props.prizes.lengthctx.clearRect(0, 0, canvas.width, canvas.height)// 绘制扇形props.prizes.forEach((prize, index) => {const startAngle = index * arc + rotationAngle.valueconst endAngle = (index + 1) * arc + rotationAngle.valuectx.beginPath()ctx.fillStyle = props.colors[index % props.colors.length]ctx.moveTo(centerX, centerY)ctx.arc(centerX, centerY, radius, startAngle, endAngle)ctx.closePath()ctx.fill()// 绘制文字和图标ctx.save()ctx.translate(centerX, centerY)ctx.rotate(startAngle + arc / 2)// 绘制文字ctx.fillStyle = '#fff'ctx.font = 'bold 14px Arial'ctx.textAlign = 'center'ctx.fillText(prize.name, radius * 0.7, 5)// 绘制图标if (loadedImages.value[index]) {const img = loadedImages.value[index]ctx.drawImage(img, radius * 0.5 - 15, -15, 30, 30)} else if (prize.icon) {ctx.fillStyle = 'rgba(255,255,255,0.7)'ctx.fillRect(radius * 0.5 - 15, -15, 30, 30)}ctx.restore()})}

5. 开始抽奖和动画效果

实现startLottery方法用于开始抽奖,通过selectPrizeByProbability方法根据概率选择中奖奖项,然后使用animateRotation方法实现转盘旋转的动画效果。动画效果中使用缓动函数控制旋转速度,使转盘先快后慢最终停在中奖区域。

const startLottery = () => {if (isRotating.value) returnemit('start')isRotating.value = trueresult.value = nullselectedPrizeIndex.value = selectPrizeByProbability()startTime = performance.now()animateRotation()}const animateRotation = () => {const now = performance.now()const elapsed = now - startTimeconst progress = Math.min(elapsed / props.duration, 1)// 缓动函数 - 先快后慢const easeOut = 1 - Math.pow(1 - progress, 4)// 计算旋转角度const anglePerItem = Math.PI * 2 / props.prizes.lengthconst fullCircles = 5 // 完整旋转圈数// 关键修正:减去Math.PI/2(90度)来校准初始位置const targetAngle = fullCircles * Math.PI * 2 +(Math.PI * 2 - anglePerItem * selectedPrizeIndex.value) -(anglePerItem / 2) -Math.PI / 2rotationAngle.value = easeOut * targetAngledrawWheel()if (progress < 1) {animationFrameId = requestAnimationFrame(animateRotation)} else {// 确保最终角度精确对准奖项中心rotationAngle.value = fullCircles * Math.PI * 2 +(Math.PI * 2 - anglePerItem * selectedPrizeIndex.value) -(anglePerItem / 2) -Math.PI / 2drawWheel()isRotating.value = falseemit('end', props.prizes[selectedPrizeIndex.value])result.value = props.prizes[selectedPrizeIndex.value]}}

6. 根据概率选择奖项

实现selectPrizeByProbability方法,计算所有奖项的总概率,然后生成一个随机数,根据随机数落在哪个概率区间来确定中奖奖项。

const selectPrizeByProbability = () => {const totalProbability = props.prizes.reduce((sum, prize) => sum + prize.probability, 0)const random = Math.random() * totalProbabilitylet currentSum = 0for (let i = 0; i < props.prizes.length; i++) {currentSum += props.prizes[i].probabilityif (random <= currentSum) {return i}}return 0}

7. 生命周期钩子和监听器

在onMounted钩子中调用loadImages和drawWheel初始化转盘;在onBeforeUnmount钩子中取消动画帧,避免内存泄漏;使用watch监听props.prizes的变化,重新加载图片并绘制转盘。

onMounted(() => {loadImages()drawWheel()})onBeforeUnmount(() => {if (animationFrameId) {cancelAnimationFrame(animationFrameId)}})watch(() => props.prizes, () => {loadedImages.value = {}loadImages()drawWheel()}, { deep: true })

8. 定义组件样式

通过 CSS 样式定义抽奖转盘组件的外观,包括转盘容器、中心按钮、指针和中奖结果提示框的样式。

.lottery-container {display: flex;flex-direction: column;align-items: center;}.wheel-container {position: relative;width: 350px;height: 350px;margin: 0 auto;}canvas {width: 100%;height: 100%;border-radius: 50%;box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);}.wheel-center {position: absolute;width: 80px;height: 80px;background: white;border-radius: 50%;top: 50%;left: 50%;transform: translate(-50%, -50%);display: flex;align-items: center;justify-content: center;cursor: pointer;z-index: 10;box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);border: 2px solid #e74c3c;}.start-btn {font-size: 16px;font-weight: bold;color: #e74c3c;text-align: center;user-select: none;}.pointer {position: absolute;width: 40px;height: 40px;top: -20px;left: 50%;transform: translateX(-50%);z-index: 10;}.pointer::before {content: '';position: absolute;border-width: 15px;border-style: solid;border-color: transparent transparent #e74c3c transparent;top: 0;left:7px;}.result-modal {position: fixed;top: 50%;left: 50%;transform: translate(-50%, -50%);background: white;padding: 30px;border-radius: 10px;box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);z-index: 100;text-align: center;}.result-modal h3 {margin-top: 0;color: #e74c3c;}.result-modal button {margin-top: 20px;padding: 10px 20px;background: #e74c3c;color: white;border: none;border-radius: 5px;cursor: pointer;font-size: 16px;}

完整代码

<template><div class="lottery-container"><div class="wheel-container"><canvas ref="canvasRef" width="350" height="350"></canvas><div class="wheel-center" @click="startLottery"><div class="start-btn">{{ isRotating ? '抽奖中...' : '开始抽奖' }}</div></div><div class="pointer"></div></div><div v-if="result" class="result-modal"><h3>恭喜您获得: {{ result.name }}</h3><button @click="result = null">确定</button></div></div>
</template><script setup>
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'const props = defineProps({prizes: {type: Array,required: true,validator: (value) => {return value.length >= 2 && value.every(item => item.name && typeof item.probability === 'number' && item.probability >= 0)}},duration: {type: Number,default: 5000},colors: {type: Array,default: () => ['#FF5252', '#FF4081', '#E040FB', '#7C4DFF','#536DFE', '#448AFF', '#40C4FF', '#18FFFF','#64FFDA', '#69F0AE', '#B2FF59', '#EEFF41','#FFFF00', '#FFD740', '#FFAB40', '#FF6E40']}
})const emit = defineEmits(['start', 'end'])const canvasRef = ref(null)
const isRotating = ref(false)
const rotationAngle = ref(0)
const selectedPrizeIndex = ref(-1)
const result = ref(null)
const loadedImages = ref({})
let animationFrameId = null
let startTime = 0// 预加载所有图片
const loadImages = () => {props.prizes.forEach((prize, index) => {if (prize.icon) {const img = new Image()img.src = prize.iconimg.onload = () => {loadedImages.value[index] = imgdrawWheel()}img.onerror = () => {console.error(`图片加载失败: ${prize.icon}`)}}})
}// 绘制转盘
const drawWheel = () => {const canvas = canvasRef.valueif (!canvas) returnconst ctx = canvas.getContext('2d')const centerX = canvas.width / 2const centerY = canvas.height / 2const radius = Math.min(centerX, centerY) - 10const arc = Math.PI * 2 / props.prizes.lengthctx.clearRect(0, 0, canvas.width, canvas.height)// 绘制扇形props.prizes.forEach((prize, index) => {const startAngle = index * arc + rotationAngle.valueconst endAngle = (index + 1) * arc + rotationAngle.valuectx.beginPath()ctx.fillStyle = props.colors[index % props.colors.length]ctx.moveTo(centerX, centerY)ctx.arc(centerX, centerY, radius, startAngle, endAngle)ctx.closePath()ctx.fill()// 绘制文字和图标ctx.save()ctx.translate(centerX, centerY)ctx.rotate(startAngle + arc / 2)// 绘制文字ctx.fillStyle = '#fff'ctx.font = 'bold 14px Arial'ctx.textAlign = 'center'ctx.fillText(prize.name, radius * 0.7, 5)// 绘制图标if (loadedImages.value[index]) {const img = loadedImages.value[index]ctx.drawImage(img, radius * 0.5 - 15, -15, 30, 30)} else if (prize.icon) {ctx.fillStyle = 'rgba(255,255,255,0.7)'ctx.fillRect(radius * 0.5 - 15, -15, 30, 30)}ctx.restore()})
}// 开始抽奖
const startLottery = () => {if (isRotating.value) returnemit('start')isRotating.value = trueresult.value = nullselectedPrizeIndex.value = selectPrizeByProbability()startTime = performance.now()animateRotation()
}// 动画函数
const animateRotation = () => {const now = performance.now()const elapsed = now - startTimeconst progress = Math.min(elapsed / props.duration, 1)// 缓动函数 - 先快后慢const easeOut = 1 - Math.pow(1 - progress, 4)// 计算旋转角度const anglePerItem = Math.PI * 2 / props.prizes.lengthconst fullCircles = 5 // 完整旋转圈数// 关键修正:减去Math.PI/2(90度)来校准初始位置const targetAngle = fullCircles * Math.PI * 2 + (Math.PI * 2 - anglePerItem * selectedPrizeIndex.value) - (anglePerItem / 2) - Math.PI / 2rotationAngle.value = easeOut * targetAngledrawWheel()if (progress < 1) {animationFrameId = requestAnimationFrame(animateRotation)} else {// 确保最终角度精确对准奖项中心rotationAngle.value = fullCircles * Math.PI * 2 + (Math.PI * 2 - anglePerItem * selectedPrizeIndex.value) - (anglePerItem / 2) - Math.PI / 2drawWheel()isRotating.value = falseemit('end', props.prizes[selectedPrizeIndex.value])result.value = props.prizes[selectedPrizeIndex.value]}
}// 根据概率选择奖项
const selectPrizeByProbability = () => {const totalProbability = props.prizes.reduce((sum, prize) => sum + prize.probability, 0)const random = Math.random() * totalProbabilitylet currentSum = 0for (let i = 0; i < props.prizes.length; i++) {currentSum += props.prizes[i].probabilityif (random <= currentSum) {return i}}return 0
}// 初始化
onMounted(() => {loadImages()drawWheel()
})onBeforeUnmount(() => {if (animationFrameId) {cancelAnimationFrame(animationFrameId)}
})watch(() => props.prizes, () => {loadedImages.value = {}loadImages()drawWheel()
}, { deep: true })
</script><style scoped>
.lottery-container {display: flex;flex-direction: column;align-items: center;
}.wheel-container {position: relative;width: 350px;height: 350px;margin: 0 auto;
}canvas {width: 100%;height: 100%;border-radius: 50%;box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
}.wheel-center {position: absolute;width: 80px;height: 80px;background: white;border-radius: 50%;top: 50%;left: 50%;transform: translate(-50%, -50%);display: flex;align-items: center;justify-content: center;cursor: pointer;z-index: 10;box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);border: 2px solid #e74c3c;
}.start-btn {font-size: 16px;font-weight: bold;color: #e74c3c;text-align: center;user-select: none;
}.pointer {position: absolute;width: 40px;height: 40px;top: -20px;left: 50%;transform: translateX(-50%);z-index: 10;
}.pointer::before {content: '';position: absolute;border-width: 15px;border-style: solid;border-color: transparent transparent #e74c3c transparent;top: 0;left:7px;
}.result-modal {position: fixed;top: 50%;left: 50%;transform: translate(-50%, -50%);background: white;padding: 30px;border-radius: 10px;box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);z-index: 100;text-align: center;
}.result-modal h3 {margin-top: 0;color: #e74c3c;
}.result-modal button {margin-top: 20px;padding: 10px 20px;background: #e74c3c;color: white;border: none;border-radius: 5px;cursor: pointer;font-size: 16px;
}
</style>

在父组件中prizes的结构为:

[{ name: '一等奖', probability: 5, icon: './public/icons/jiang.png' },{ name: '二等奖', probability: 10, icon: './public/icons/jiang.png' },{ name: '三等奖', probability: 15, icon: './public/icons/jiang.png' },{ name: '四等奖', probability: 20, icon: './public/icons/jiang.png' },{ name: '五等奖', probability: 25, icon: './public/icons/jiang.png' },{ name: '谢谢参与', probability: 25 }]

 

 


文章转载自:

http://00000000.bzpwh.cn
http://00000000.bzpwh.cn
http://00000000.bzpwh.cn
http://00000000.bzpwh.cn
http://00000000.bzpwh.cn
http://00000000.bzpwh.cn
http://00000000.bzpwh.cn
http://00000000.bzpwh.cn
http://00000000.bzpwh.cn
http://00000000.bzpwh.cn
http://00000000.bzpwh.cn
http://00000000.bzpwh.cn
http://00000000.bzpwh.cn
http://00000000.bzpwh.cn
http://00000000.bzpwh.cn
http://00000000.bzpwh.cn
http://00000000.bzpwh.cn
http://00000000.bzpwh.cn
http://00000000.bzpwh.cn
http://00000000.bzpwh.cn
http://00000000.bzpwh.cn
http://00000000.bzpwh.cn
http://00000000.bzpwh.cn
http://00000000.bzpwh.cn
http://00000000.bzpwh.cn
http://00000000.bzpwh.cn
http://00000000.bzpwh.cn
http://00000000.bzpwh.cn
http://00000000.bzpwh.cn
http://00000000.bzpwh.cn
http://www.dtcms.com/wzjs/604910.html

相关文章:

  • 万网域名指向网站国家建设标准发布网站在哪里
  • 网站建设案例简介怎么写软文案例500字
  • 网站vr视角怎么做wordpress会员
  • 网站建设套模制作网页需要的技术
  • 淄博网站制作公司服务深圳搜豹网站建设公司
  • asp 手机网站设计类专业需要艺考吗
  • 网站怎么做微信送红包活动网站做优化有什么好处
  • 分销网站怎么做现在建网站挣钱吗
  • 平凉城乡建设局网站北京哪里做网站
  • 购物网站建设特色绩溪做网站
  • 我的文档上传到网站 做链接做旅游网站赚钱吗
  • 泰安新闻出版小镇连云港网站关键字优化
  • 网站设计的步骤wordpress模板导出
  • 东莞网站建设aj平台式网站模板下载
  • 建站免费建站平台高端电子网站建设
  • 上海做网站品牌家具网站建设规划书
  • 网站建设实验作业汝州建设局网站
  • 大同做网站2013电子商务网站建设
  • 代驾网站开发如何做外贸网站
  • 怎么清理网站后门文件菜鸟html教程
  • 如何自己做软件网站中国小康建设网官方网站
  • 做绿植o2o网站怎么样深圳制作网站建设推广
  • 万网域名怎么绑定网站网站报备查询
  • php网站的部署个人网站建设赚取流量费
  • 响应式网站好吗wordpress重置后导航没反应
  • 做啥网站比较好赚钱容桂网站建设联系方式
  • 杭州做外贸网站wordpress h5制作插件
  • 沈阳定制网站开发wordpress 提交熊掌
  • 网站设计公司北京adapt wordpress
  • 佛山公司网站设计个人网站如何搭建