ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

在 uni-app 中集成 lucky-canvas:大转盘 / 九宫格 / 老虎机跨端抽奖插件实战指南

在 uni-app 中集成 lucky-canvas:大转盘 / 九宫格 / 老虎机跨端抽奖插件实战指南
  • 前端
  • UI组件

【免费下载链接】lucky-canvas

🎖🎖🎖 基于 TS + Canvas 开发的【大转盘 / 九宫格 / 老虎机】抽奖插件,🌈 一套源码适配多端框架 JS / Vue / React / Taro / UniApp / 微信小程序等,🎨 奖品 / 文字 / 图片 / 颜色 / 按钮均可配置,支持同步 / 异步抽奖,🎯 概率前 / 后端可控,🚀 自动根据 dpr 调整清晰度适配移动端

项目地址:https://gitcode.com/gh_mirrors/luc/lucky-canvas
点击查看免费下载

导读:lucky-canvas 是一套基于 TypeScript 与 Canvas 的跨平台抽奖插件(大转盘 / 九宫格 / 老虎机),本文聚焦其官方 uni-app 适配包@lucky-canvas/uni,完整讲解在 uni-app 项目中的安装、组件注册、属性配置、方法调用与多端适配原理。读完本文,你将掌握在 H5 / 微信小程序等多端环境下快速接入三种抽奖组件,并理解 rpx 单位换算、dpr 清晰度适配、原生组件层级等底层机制,可直接落地到自己的抽奖业务中。


一、插件定位与安装方式

packages/uni/目录是 lucky-canvas 面向 uni-app 的官方封装,核心文件为三个 Vue 组件(lucky-wheel.vue 大转盘、lucky-grid.vue 九宫格、slot-machine.vue 老虎机)以及工具模块 utils.js。根据 packages/uni/package.json,该包名为@lucky-canvas/uni,并依赖核心渲染引擎lucky-canvas(~1.7.19),安装时会自动带入依赖。

uni-app 下有两种安装途径:

1. 通过 HBuilderX 插件市场导入

在 HBuilderX 中打开插件市场页面,搜索 “lucky-canvas” 即可一键导入插件。导入后组件代码会进入项目的components/@lucky-canvas/uni/目录,导入路径与 npm 方式不同(见下文)。

2. 通过 npm / yarn 安装

# npm 安装: npm install @lucky-canvas/uni # yarn 安装: yarn add @lucky-canvas/uni

npm 安装后包默认落在node_modules中,直接以包名引入即可,无需关心物理路径。


二、在页面中引入并注册组件

1. 模板中使用组件

@lucky-canvas/uni暴露的组件是标准的 uni-app 自定义组件,可直接在<view>中按需放置,宽度高度通常使用rpx单位,也支持px:

<view> <!-- 大转盘抽奖 --> <LuckyWheel width="600rpx" height="600rpx" :blocks="blocks" :prizes="prizes" :buttons="buttons" :defaultConfig="defaultConfig" :defaultStyle="defaultStyle" @start="handleStart" @end="handleEnd" /> <!-- 九宫格抽奖 --> <LuckyGrid width="600rpx" height="600rpx" :rows="rows" :cols="cols" :blocks="blocks" :prizes="prizes" :buttons="buttons" :button="button" :defaultConfig="defaultConfig" :defaultStyle="defaultStyle" :activeStyle="activeStyle" @start="handleStart" @end="handleEnd" /> <!-- 老虎机抽奖 --> <SlotMachine width="600rpx" height="600rpx" :blocks="blocks" :prizes="prizes" :slots="slots" :defaultConfig="defaultConfig" :defaultStyle="defaultStyle" @end="handleEnd" /> </view>

说明:三款组件在源码中分别以name: 'lucky-wheel'、name: 'lucky-grid'、name: 'slot-machine'注册,可自由改造成任意符合 uni-app 命名规范的标签名。

2. JS 中引入与注册

// npm 下载会默认到 node_modules 里面,直接引入包名即可 import LuckyWheel from '@lucky-canvas/uni/lucky-wheel' // 大转盘 import LuckyGrid from '@lucky-canvas/uni/lucky-grid' // 九宫格 import SlotMachine from '@lucky-canvas/uni/slot-machine' // 老虎机 // 如果你是通过 HBuilderX 导入插件,那你需要指定一下路径 // import LuckyWheel from '@/components/@lucky-canvas/uni/lucky-wheel' // 大转盘 // import LuckyGrid from '@/components/@lucky-canvas/uni/lucky-grid' // 九宫格 // import SlotMachine from '@/components/@lucky-canvas/uni/slot-machine' // 老虎机 export default { // 注册组件 components: { LuckyWheel, LuckyGrid, SlotMachine }, }

两种引入方式的差异点在于:npm 安装时组件文件位于node_modules/@lucky-canvas/uni/下,包名映射可直接命中;HBuilderX 插件方式则需使用@/components/@lucky-canvas/uni/...的相对路径。官方还提供了完整的 uni-app demo,位于官方文档站点的 uni-app 页面,可作为首次尝试的参考模板。


三、组件属性与核心配置详解

1. 三个组件的 Props 一览

结合 lucky-wheel.vue、lucky-grid.vue、slot-machine.vue 的 props 声明,可整理如下:

属性LuckyWheelLuckyGridSlotMachine类型默认值说明
width✔✔✔String'600rpx'组件宽度,支持 rpx / px
height✔✔✔String'600rpx'组件高度,支持 rpx / px
rows/cols–✔–String/Number3/3九宫格行数、列数
blocks✔✔✔Array[]背景层配置(内边距、背景、图片)
prizes✔✔✔Array[]奖品配置(文字、图片、背景、概率区间)
buttons✔✔✔Array[]抽奖按钮配置
button–✔–Objectundefined九宫格“单个按钮”配置(可与 buttons 并存)
slots––✔Array[]老虎机滚动轴配置(顺序、速度、方向)
defaultConfig✔✔✔Object{}抽奖动画默认配置(速度、加速度、减速时间等)
defaultStyle✔✔✔Object{}奖品默认样式(字体、颜色、背景等)
activeStyle–✔–Object{}九宫格中奖格子高亮样式

所有数据属性在组件内部都通过watch深度监听,运行期动态修改数组或对象会实时触发重绘(详见 lucky-wheel.vue 与 lucky-grid.vue 的 watch 实现),因此抽奖数据完全可以由后端接口异步返回后再绑定。

2. 单位换算机制:rpx 与 px 的自动转换

三款组件在挂载时都会调用changeUnits(this.width)把模板里传入的字符串尺寸换算成像素,核心逻辑在 utils.js:

let windowWidth = uni.getSystemInfoSync().windowWidth // uni-app@2.9起, 屏幕最多适配到960, 超出则按375计算 if (windowWidth > 960) windowWidth = 375 export const rpx2px = (value) => { if (typeof value === 'string') value = Number(value.replace(/[a-z]*/g, '')) return windowWidth / 750 * value } export const changeUnits = (value) => { value = String(value) return Number(value.replace(/^(\-*[0-9.]*)([a-z%]*)$/, (value, num, unit) => { switch (unit) { case 'px': num *= 1; break case 'rpx': num = rpx2px(num); break default: num *= 1; break } return num })) }

要点:rpx2px按设计稿 750 宽度基准等比换算;从 uni-app 2.9 起屏幕宽度超过 960 时按 375 计算,保证大屏下渲染尺寸可控。该换算函数还会作为unitFunc传入核心引擎,使blocks、prizes等配置内部的top、left、fontSize等带单位的值也能统一换算(见 lucky-wheel.vue)。

3. defaultConfig:抽奖动画的核心参数

defaultConfig控制旋转/游走动画的行为,各字段类型定义在核心类型文件 packages/core/src/types/wheel.ts、packages/core/src/types/grid.ts、packages/core/src/types/slot.ts 中。大转盘的默认值与运行逻辑位于 packages/core/src/lib/wheel.ts:

字段默认值说明
gutter'0px'奖品之间的间隙
offsetDegree0奖品区域偏移角度
speed20旋转速度
speedFunction'quad'缓动函数类型
accelerationTime2500加速阶段时长(ms)
decelerationTime2500减速阶段时长(ms)
stopRange0停止时的随机偏移范围

九宫格的默认值见 packages/core/src/lib/grid.ts:gutter默认5、speed默认20(内部再除以 40 作为游走步进)、accelerationTime/decelerationTime默认2500。老虎机的defaultConfig则额外支持mode(vertical纵向 /horizontal横向)、direction(1与-1控制正反向滚动)、rowSpacing与colSpacing(行列间距)。

从 wheel.ts 的run()帧循环可以看出动画分为四个阶段:step=0未开始、step=1加速(quad.easeIn插值)、step=2匀速、step=3减速(quad.easeOut插值),这是accelerationTime/decelerationTime直接参与运算的位置。

4. defaultStyle / activeStyle:文字与背景样式

奖品文字统一支持fontColor、fontSize、fontStyle、fontWeight、lineHeight、wordWrap(自动换行)、lengthLimit(换行宽度限制)、lineClamp(最多行数)等字段(见 packages/core/src/types/index.ts 的FontItemType/FontExtendType)。大转盘defaultStyle默认值包括fontSize: '18px'、fontColor: '#000'、fontStyle: 'sans-serif'、fontWeight: '400'、background: 'rgba(0,0,0,0)'、wordWrap: true、lengthLimit: '90%'(wheel.ts);九宫格在此基础上默认borderRadius: 20、shadow: '',且额外提供activeStyle(中奖高亮样式,默认background: '#ffce98'),见 grid.ts。

5. 奖品、区块与按钮结构

  • blocks(背景层):大转盘支持padding、background、imgs;九宫格/老虎机额外支持borderRadius与四边独立paddingTop/Right/Bottom/Left。
  • prizes(奖品层):大转盘奖品含range(中奖区间,配合stop(index)或概率区间计算)、background、fonts、imgs;九宫格奖品基于格子坐标x/y,可扩展col/row跨格、disabled禁用等。
  • buttons(按钮层):大转盘按钮支持radius、pointer(是否带指针)、background、fonts、imgs;九宫格按钮同格子一样支持x/y/col/row定位,并可配置callback。

可参考核心包的真实示例 packages/core/examples/wheel.html 与 packages/core/examples/grid.html 快速理解各层配置的组合方式。


四、方法调用与事件回调

1. 实例方法

三款组件均向父级暴露以下方法(见 lucky-wheel.vue、lucky-grid.vue、slot-machine.vue):

// 通过 ref 拿到组件实例 this.$refs.luckyWheel.init() // 重新初始化(重置状态并重绘) this.$refs.luckyWheel.play() // 开始抽奖(触发加速 → 匀速) this.$refs.luckyWheel.stop(2) // 停止抽奖,index 为中奖奖品索引 this.$refs.luckyWheel.stop(-1) // 传负值则直接取消本次抽奖

核心层对play()/stop()的语义定义见 wheel.ts:stop(index)未传 index 时,会依据各奖品range字段通过computeRange计算一个中奖索引,实现“前端可控概率”;传入index < 0则终止游戏。

2. 事件回调:start / end

组件内部把核心层的开始/结束回调通过$emit转发给使用方:

start: (...rest) => { this.$emit('start', ...rest) }, end: (...rest) => { this.$emit('end', ...rest) this.hideCanvas() // 小程序端把 canvas 转成图片隐藏,规避层级问题 },

因此父组件可以这样监听结果:

export default { methods: { handleStart () { // 抽奖开始:可以在此发起网络请求,拿到中奖索引后调用 stop(index) setTimeout(() => { this.$refs.luckyWheel.stop(2) // 示例:停止到第 3 个奖品 }, 1000) }, handleEnd (prize) { // 抽奖结束:prize 为命中的奖品对象,用于弹窗提示等 console.log('中奖结果:', prize) }, }, }

3. 按钮点击:startCallback

大转盘与九宫格的中心/格子按钮是透明的原生覆盖层(cover-view),点击后由组件内部调用myLucky.startCallback(...)(见 lucky-wheel.vue、lucky-grid.vue)。此时若配置了start回调,则自动进入抽奖流程;若想完全由业务侧控制,也可不配置start,转而通过ref.play()手动触发。


五、多端渲染原理:dpr 适配与原生组件层级

1. Canvas 2D 节点获取与 dpr 缩放

三款组件统一使用type="2d"的新版 canvas,并在mounted后通过uni.createSelectorQuery().in(this).select('#lucky-wheel').fields({ node: true, size: true })拿到 Canvas 节点(lucky-wheel.vue)。拿到节点后:

  • 读取uni.getSystemInfoSync().pixelRatio作为设备像素比 dpr;
  • 在小程序端执行canvas.width = width * dpr并按ctx.scale(dpr, dpr)缩放画布,保证转盘在 Retina 屏上清晰不模糊;
  • H5 端则直接复用requestAnimationFrame驱动帧循环,并额外用document.querySelector兜底获取 canvas。

这些 dpr 逻辑对应核心引擎 packages/core/src/lib/lucky.ts 的setDpr()——窗口环境自动取devicePixelRatio,其余环境(小程序等)必须手动传入 dpr,uni 封装正是这么做的。

2. 小程序端:canvas 转图片隐藏

小程序中 canvas 属于原生组件、永远置顶渲染,普通view无法覆盖。uni 封装的处理思路是:抽奖结束后调用uni.canvasToTempFilePath把当前画布内容导出为临时图片,再以<image>覆盖显示(见 utils.js 的getImage()与各组件的hideCanvas());下一次play()开始时(afterStart回调)清空imgSrc恢复 canvas 动画。这样既解决了“弹窗盖不住转盘”的层级痛点,也避免了 canvas 常驻带来的性能开销。

3. 按钮点击层:cover-view / view 的分端切换

模板中使用条件编译做了平台区分(lucky-wheel.vue):

<!-- #ifdef APP-PLUS --> <view class="lucky-wheel-btn" @click="toPlay" ...></view> <!-- #endif --> <!-- #ifndef APP-PLUS --> <cover-view class="lucky-wheel-btn" @click="toPlay" ...></cover-view> <!-- #endif -->

即小程序端按钮使用可覆盖原生组件的cover-view,其他端(H5)使用普通view。大转盘按钮的尺寸由核心层在afterInit回调中依据maxBtnRadius动态计算并写入组件状态(lucky-wheel.vue),九宫格的多个按钮则通过getGeometricProperty计算每个按钮的几何位置后渲染(lucky-grid.vue)。

4. 图片资源的加载

由于小程序 canvas 无法直接绘制网络图片,组件在小程序端会把blocks/prizes/buttons配置里的图片源渲染成隐藏的<image>标签,待其load完成后通过canvas.createImage()与onload回调把图片对象注入渲染引擎(utils.js 的resolveImage)。九宫格还额外支持activeSrc中奖态图片,加载后存入$activeResolve供高亮时切换(lucky-grid.vue)。


六、常见问题与排查指引

  1. 转盘层级太高了,我的弹窗盖不住怎么办?因为小程序里 canvas 是原生组件顶层渲染,组件无法控制 canvas 的层级。想盖住它也简单:一方面组件本身在抽奖结束后会把 canvas 转成图片以降低层级冲突;另一方面可以用cover-view/cover-image这类原生组件来承载弹窗内容,让弹窗同样处于原生层。

  2. 官网里的素材、图片组件从哪下载?官网中展示的任何图片素材均为学习交流使用,请勿将其用于商业用途,由此产生的任何商业纠纷需自行承担。

  3. xxx 属性怎么使用?xxx 方法怎么调用?优先查阅官方文档站点对应组件的属性/方法/事件章节,本文第三、四节也整理了 props 与方法的速查表,可按需对照。

  4. 这个属性的效果与官网描述不一致?可能是版本差异或 bug,建议先在本地确认核心包与 uni 包的版本(见 packages/uni/package.json 的依赖锁定),再到 GitHub 仓库的 issues 中提问,并认真填写 issue 模板(包含复现场景、配置与截图)。

  5. 为什么这个插件不支持 app 和其他小程序?最新版暂不支持 App 端:组件在APP-PLUS环境下会输出提示该抽奖插件的最新版暂不支持app端, 请通过npm安装旧版本【npm i uni-luck-draw@1.3.9】(见 lucky-wheel.vue)。如果你有意参与 uni-app 的多端兼容开发,可以基于本仓库源码自行扩展。


七、平台兼容性小结

从各组件的条件编译代码可确认当前兼容矩阵:

平台大转盘九宫格老虎机
H5✔✔✔
微信小程序(MP-WX)✔✔✔
App(APP-PLUS)✖(提示安装旧版)✖(提示安装旧版)✖

H5 端引擎以flag: 'WEB'+requestAnimationFrame驱动,小程序端以flag: 'MP-WX'+canvas 2d 节点 + dpr 缩放驱动;老虎机在小程序端还额外通过uni.createOffscreenCanvas({ type: '2d' })创建离屏画布用于滚动内容渲染(slot-machine.vue)。如果你需要在 App 端使用,可按官方提示回退到旧版uni-luck-draw@1.3.9,或关注后续版本的多端适配进展。

结合 packages/uni/README.md 与本文整理的源码依据,你现在可以按照“安装 → 引入 → 配置 → 监听事件 → 调用 play/stop”的完整链路,在 uni-app 项目中快速搭建自己的跨端抽奖功能了。

  • 前端
  • UI组件

【免费下载链接】lucky-canvas

🎖🎖🎖 基于 TS + Canvas 开发的【大转盘 / 九宫格 / 老虎机】抽奖插件,🌈 一套源码适配多端框架 JS / Vue / React / Taro / UniApp / 微信小程序等,🎨 奖品 / 文字 / 图片 / 颜色 / 按钮均可配置,支持同步 / 异步抽奖,🎯 概率前 / 后端可控,🚀 自动根据 dpr 调整清晰度适配移动端

项目地址:https://gitcode.com/gh_mirrors/luc/lucky-canvas
点击查看免费下载

相关推荐

上一篇:beautiful-react-hooks 之 useMouse:一次获取鼠标位置与全部鼠标事件的组合 Hook
下一篇:ZTE光猫终极管理指南:5分钟解锁工厂模式,永久获得Telnet访问权限

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进