十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

微信小程序二维码生成器:本地canvas绘制组件源码解析

微信小程序二维码生成器:本地canvas绘制组件源码解析 简介微信小程序二维码生成器是一份面向小程序开发者的可直接运行源码资源适用于需要在微信生态内快速实现二维码生成、展示与分享的场景。该项目基于原生 JavaScript 开发完整呈现小程序从页面搭建到逻辑控制的实现链路可直接导入微信开发者工具预览调试也可作为学习原生组件组织、事件绑定与工具方法封装的参考案例。压缩包共 15 个文件以 js、json、wxss、wxml 文件为主分别承担逻辑控制、全局配置、样式定义与视图结构另含 2 个 png 图片用于界面展示整体包体仅 48KB结构精简、易于二次修改。资源上传以来已有 4630 人学习浏览受到较多开发者关注。借助这份代码读者能够掌握二维码生成工具的集成思路、小程序的目录规划方式以及接口调用与参数处理的方法同时可根据自身需求快速替换生成接口或调整样式适合作为入门练手与项目改造的基础模板。 直接开始写正文。注意段落长度要够语言要自然。1. 这个二维码生成器到底是怎么来的做微信小程序开发这几年二维码相关的需求几乎每个项目都会遇到商品推广码、活动签到码、订单核销码、桌台码、小程序路径码……如果你去翻各大开源平台能找到的现成方案无非两类一类是调用第三方云服务生成图片另一类是本地引入JS库自己画。前一种受制于接口免费次数和网络环境后一种则是把生成逻辑全塞进小程序里跑完全离线、不依赖外部服务对用户来说体感更好。我这个项目做的就是第二种一个微信小程序内的二维码生成器组件源码支持自定义内容文本、尺寸、颜色、logo图标最终输出为可以直接长按识别或保存到相册的二维码图片。它不是“调一下外部接口返回图片”的demo而是真正把二维码生成算法跑在了小程序环境里源码拿到手就能二次开发。适合谁来参考如果你正在做小程序电商、门店系统、活动工具类产品需要一个干净的二维码生成模块或者你打算深入理解canvas绘制、组件封装、小程序端图片导出这一套流程这份源码都值得花半小时仔细看一遍。下面前四个章节分别覆盖方案选型、核心原理、完整实现、踩坑实录最后一节再聊聊后续可以怎么扩展。2. 方案选型为什么不直接调接口2.1 云服务接口的隐藏问题很多初学者拿到需求第一反应是去接聚合数据、极速数据这类二维码API参数传进去返回一张图片URL放到上就完事。短期跑demo确实爽但放到生产环境有几个坑是躲不掉的免费套餐每天只有几十次调用额度一旦量起来就要付费接口服务器出故障或者被恶意刷的时候整个二维码模块跟着瘫痪更麻烦的是返回的图片URL是外链在微信小程序里渲染外链图片本身没问题但如果之后要做离线包、隐私保护、审核排查外链域名白名单和内容校验都是额外成本。还有一个常被忽略的问题接口返回的二维码图片一般是服务端按固定参数渲染的想自定义中间logo、调整颜色、设置边距会很麻烦每次改动都要拼URL参数很多接口根本不支持这么细的定制。这些都是我选本地生成方案的最直接原因。注意用本地生成方案二维码数据完全在小程序内计算不经过第三方服务器用户扫码后的路径跳转、参数解析也不需要额外安全审查这点在审核某些类目时反而占优势。2.2 主流本地生成方案对比既然决定本地生成可选的路线就以下三种我按实际踩过的体验对比一下。方案原理优点缺点weapp-qrcodees6库纯JS计算矩阵用canvas绘制极轻量、无依赖、老项目兼容好维护停滞对新canvas类型支持需自己适配qrcode.js 自己封装标准qrcode算法适配到小程序算法成熟、可控性强需要理解核心代码封装门槛略高服务端生成后传给小程序Node/PHP生成图片定制能力最强增加服务器维护与网络依赖违背离线原则我最终选择的是第二种把qrcode生成算法整理成一份干净的小程序模块然后自己封装canvas绘制逻辑。原因很简单weapp-qrcode虽然开箱即用但你很难改动它的码型生成逻辑而我自己整理这份源码加logo、调容错率、改颜色、输出透明背景每一处都知道在哪改出问题也能迅速定位。3. 二维码生成的核心原理以及为什么小程序能跑3.1 二维码不只是黑白格子很多人以为二维码就是把字符串转成图片其实完整的生成过程大致是数据编码、纠错码计算、矩阵构建、掩码处理、格式信息填充。其中纠错码用的是Reed-Solomon算法它决定了二维码在部分被遮挡或磨损时仍然能扫出来。这也是为什么你能在二维码中间放一个logo只要遮挡面积不超过容错级别允许的比例扫码照样成功。小程序端生成二维码不需要自己实现完整的Reed-Solomon只需要把成熟的库移植或精简之后跑在JSCore上。因为二维码生成算法是纯CPU计算不涉及DOM、不需要网络运算量也很小哪怕是最低端安卓机生成一张二维码耗时也在几十毫秒以内完全不用担心性能问题。3.2 Canvas小程序绘制的核心出口计算出二维码矩阵之后需要用Canvas把这些黑白格子画出来。小程序里的canvas组件有两套渲染体系老版的配合wx.createCanvasContext新版的配合canvas.getContext(2d)。老版API简单但功能有限新版更贴近Web Canvas标准而且在高分屏下清晰度更好。我这里选择的是新版Canvas 2D接口。核心原因是它支持物理像素绘制你可以用wx.getSystemInfoSync().pixelRatio拿到设备像素比再把canvas画布的实际尺寸乘以这个比值这样渲染出来的二维码就不是糊的尤其在高分屏手机上效果差异非常明显。这段逻辑稍后会在源码里详细展示。补充知识Canvas 2D在iOS和安卓上的createImage、drawImage行为略有差别画完二维码后如果要加到图片里导出务必在绘制完成的回调里再操作不能靠setTimeout糊弄否则截屏时机不稳。4. 完整源码实现从矩阵计算到图片导出4.1 项目文件结构先看下这个二维码生成器组件的文件目录。整体上是一个自定义组件放到任意小程序项目的components/qrcode下就能直接用。components/qrcode/ ├── index.js // 组件逻辑负责接收参数、调用生成方法 ├── index.json // 组件配置 ├── index.wxml // 组件模板包含canvas节点 ├── index.wxss // 组件样式 └── qrcode.js // 核心算法矩阵计算、纠错码、绘制方法组件化是我特别坚持的一点。日常开发里二维码经常出现的位置不固定可能在一个弹窗里也可能在订单详情页底部甚至同一个页面要同时渲染好几个二维码。如果每次复制粘贴一大段生成代码就会非常痛苦。封装成组件之后只需要在页面json里注册然后这样调用即可qrcode texthttps://example.com/page?id123 size200 logo/images/logo.png /4.2 核心生成逻辑代码解析下面这段是qrcode.js里最核心的绘制部分。在完整源码里二维码矩阵的数据结构是一个二维数组或者一维数组配合行列计算每个值代表该位置是否是黑色模块。得到矩阵之后需要按照QR规范预留出定位图案、校正图案、时序图案的位置再填入真实数据。我这里给一个简化但可运行的核心绘制片段方便读者看懂整体思路// 绘制二维码到canvas 2d function drawQrcode(canvas, matrix, options) { const ctx canvas.getContext(2d) const size options.size const margin options.margin || 20 // 边距/白边单位px const cellCount matrix.length const cellSize (size - margin * 2) / cellCount // 清空画布 ctx.clearRect(0, 0, size, size) // 画背景 ctx.fillStyle options.backgroundColor || #ffffff ctx.fillRect(0, 0, size, size) // 画码点。这里不用fillRect遍历每一个格子而是把一行中连续的黑色格子合并绘制性能更好 for (let row 0; row cellCount; row) { let col 0 while (col cellCount) { if (matrix[row][col]) { const startCol col while (col cellCount matrix[row][col]) { col } const x margin startCol * cellSize const y margin row * cellSize const w (col - startCol) * cellSize ctx.fillStyle options.color || #000000 ctx.fillRect(x, y, w, cellSize) } else { col } } } }这段代码有一个细节值得讲我没有一个格子一个格子地画而是把同一行里连续的黑格子合并成一个矩形来画。二维码的矩阵通常有几十上百个格子如果每个格子单独调一次fillRect绘制指令数量会多出不少合并后指令数能减少一半以上在低端机上也能保持流畅。算矩阵的核心是qrcode算法本身包括BCH纠错、块划分、掩码选择等这部分代码较长源码里直接整理自开源算法按MIT协议保留版权即可。重点说一下掩码的处理QR规范定义了8种掩码方案生成时需要遍历每种掩码计算惩罚分数选择惩罚分数最小的掩码这一步直接决定二维码能否被主流扫码器稳定识别。不少简化版源码会固定掩码省掉这一步看起来功能正常但遇到打印不清楚的纸面二维码时识别率会明显下降。4.3 组件参数与事件设计组件对外暴露的参数我设计成以下这组兼顾了日常使用的灵活性和默认值的简单性。参数类型默认值说明textString必填二维码携带的内容可以是网址、文本、JSON字符串sizeNumber200二维码输出尺寸不含padding单位pxcolorString#000000码点颜色backgroundColorString#ffffff背景颜色logoString中间logo的图片路径不传则纯二维码logoSizeNumber40logo尺寸单位pxmarginNumber20白边大小QR规范建议至少4个模块宽度在组件内部observers监听这些参数的变动任何一个参数变了就重新调用生成方法。这里有个容易被忽视的点text如果是动态变化的比如订单号、活动id一定要在observers里做延迟处理避免在页面数据刚赋值但canvas节点还没渲染完成时就开始画那样会拿到空的canvas上下文。事件方面我只暴露了一个ready事件在二维码画完之后触发并把临时文件路径带出去。后面要保存到相册或者做分享卡片都可以在这个事件里处理this.triggerEvent(ready, { tempFilePath })4.4 canvas 2d节点的正确初始化方式初始化canvas上下文是小程序里最容易踩坑的一步。很多老代码还在用wx.createCanvasContext(id)那种方式在新版基础库仍然兼容但有两个明显问题一是拿不到pixelRatio在Retina屏幕上画出来的字和线条边缘发虚二是不支持部分较新的Canvas特性。我推荐的初始化方式如下// index.js 组件内 initCanvas() { const query this.createSelectorQuery() query.select(#qrcode-canvas) .fields({ node: true, size: true }) .exec((res) { if (!res || !res[0] || !res[0].node) { console.error(canvas节点未找到) return } const canvas res[0].node const dpr wx.getSystemInfoSync().pixelRatio canvas.width res[0].width * dpr canvas.height res[0].height * dpr const ctx canvas.getContext(2d) ctx.scale(dpr, dpr) // 保存canvas和ctx实例 this.canvas canvas this.ctx ctx this.generate() }) }这段代码的关键点是canvas.width 节点宽度 * dpr。举个例子组件里canvas节点CSS宽度是200px如果不乘dpr实际画布就这么大在iPhone Xdpr约为3上渲染出来就会模糊乘了dpr之后画布实际是600x600物理像素再通过ctx.scale(dpr, dpr)把逻辑坐标系恢复正常这样绘制时用的坐标仍是200x200但输出图像清晰度会被拉高到物理像素级别。提示fields({ node: true, size: true })这里必须两个字段都要只拿node不拿size的话你就不知道canvas在页面里的实际布局尺寸后面计算width、height会出错。5. 实操过程中遇到的三个典型问题5.1 生成的二维码扫码没反应这是我调试时遇到的最诡异问题生成出来的图片用微信扫描完全没反应但用浏览器扫却偶尔能出来。排查过程很折磨人最后定位到是掩码处理被我省略了。最初的简化版本为了省事固定用了某一种掩码不参与惩罚分数计算。结果就是当内容比较短、二维码版本低时恰好生成了一些有规律排布的码点部分扫码器在图像预处理阶段会把这些规律误判成背景噪声。解决办法就是老老实实把掩码遍历的完整逻辑加回来给每种掩码算一遍惩罚分数选最低的。这个过程大概多了30行代码却直接决定了二维码在各种扫码器下的兼容性。经验总结凡是涉及到编码生成类的库最好不要自己去精简算法逻辑尤其是格式信息、掩码、纠错等级这些看起来“多余”的部分。它们都是标准里定义好的缺了哪一块短期内可能看不出来一旦真实用户用各种安卓机、廉价扫码枪扫你的码问题就全冒出来了。5.2 带logo的二维码在低端机上绘制耗时过高初期版本绘制logo是直接在生成完二维码后再用ctx.drawImage在中心画一张图片。逻辑上没毛病但有个性能问题如果logo图片是本地路径每次重绘都要重新加载一次再加上drawImage本身需要解码在低端安卓机上生成一张带logo的二维码有时候会卡到几百毫秒如果用户连续输入了几次内容页面会明显卡顿。优化方案是给logo做一层缓存首次绘制时用wx.getImageInfo把图片解码成临时路径并缓存起来后续重绘直接拿缓存的图片路径绘制避免重复解码。另一个小优化是在logo位置绘制一个白色圆角背景再把logo盖上去这样可以避免二维码码点从logo边缘露出来观感更加整洁同时也能提高识别率因为背景色隔离了logo和码点之间的干扰。5.3 保存图片到相册时出现黑色背景把二维码导出为图片时一开始我用的是wx.canvasToTempFilePath然后在canvas节点上设置了透明背景。导出之后保存到相册发现图片背景变成了黑色。原因很简单canvasToTempFilePath默认生成的是PNG带透明通道的图片但某些相册/分享场景下显示透明背景时会渲染成黑色。解决办法有两个一是在画二维码时就先画一层白色背景色块就像前面核心代码里那样先fillRect一层白色确保导出图的背景是不透明的二是在导出时手动指定fileType: png不要用jpg格式因为jpg不支持透明反而会强制抠掉透明区域。我最后选择的是第一种方案画布上彻底不透明在任何场景下都不会出幺蛾子。问题原因解决方案扫码无反应掩码处理被省略补全惩罚分数计算选择最优掩码绘制卡顿logo图片重复解码缓存getImageInfo结果避免重复解析导出黑底透明通道被渲染成黑色先绘制不透明底色或使用png格式导出6. 后续扩展方向这个组件在现有项目里已经稳定跑了大半年除了最基础的二维码展示之外我还基于它扩展了两个能力。一个是批量生成传入一个数组循环调用组件实例配合wx.nextTick控制渲染节奏一口气生成几十张带不同参数的二维码然后统一调用wx.canvasToTempFilePath导出。这个逻辑如果放到服务端做需要排队、需要高频请求放到小程序端本地做反而简单只要注意分批渲染避免一次性创建太多canvas导致内存占用过高。另一个是分享卡片合成二维码画好之后再用ctx.drawImage把背景图、标题文字、二维码区域拼到一张大图上直接生成一张适合发朋友圈或微信群的分享卡片。这里要注意文字在canvas上的绘制宽度是没法自动换行的封装一个wrapText方法把长标题按字符宽度切开逐行绘制。这两块代码我已经整理进源码里了拿到整个组件之后可以直接看图改。其实做工具类的小程序组件最大的收益就是沉淀出一套可以复用的模板下次再遇到“生成某某码”“导出某某图”的需求改改参数就行不必从零开始。最后分享一个个人的建议这套源码里的二维码算法部分我用的是开源协议下的成熟方案工业级识别率有保障。如果你也打算在自己的项目里集成二维码生成尽量别从零研究QR编码规范用现成算法再改改UI输出是最稳妥、收益最高的路线。本文还有配套的精品资源点击获取
返回列表