一、项目概述
1.1 业务场景
小程序中电子签名、涂鸦画板、手绘答题等功能,都依赖画布能力。小程序旧版 Canvas 已经停止维护,官方推荐使用type="2d"的新版 Canvas 组件。新版 API 和旧版差异较大,新手很容易出现上下文获取失败、画面模糊、兼容性异常等问题。
1.2 实现功能
- 手指触摸画布,滑动自由手绘涂鸦
- 点击按钮循环切换画笔颜色,旧线条保持原有颜色,新线条使用选中颜色
- 一键清空画布所有绘制内容
- 适配设备像素比,解决手机端绘图线条模糊
- 界面实时展示当前画笔颜色预览,状态直观可见
注意:
canvas type="2d"在 skyline 渲染器会存在兼容问题,本项目使用webview渲染器保障稳定运行。
1.3 项目目录结构
canvas组件/ ├── app.js ├── app.json ├── app.wxss └── pages └── index ├── index.js ├── index.wxml └── index.wxss二、完整代码实现
2.1 全局配置 app.json
核心配置:设置渲染器为webview,解决 canvas2d 兼容问题
{ "pages": [ "pages/index/index" ], "window": { "navigationBarTitleText": "canvas组件" }, "renderer":"webview", "sitemapLocation": "sitemap.json" }2.2 app.wxss
page { background-color: #ffffff; }2.3 页面结构 index.wxml
绑定画布触摸事件,放置操作按钮、颜色预览组件
<view class="box"> <view class="title">自由绘图</view> <canvas type="2d" id="myCanvas" bindtouchstart="paintStart" bindtouchmove="paintMove" ></canvas> <view class="btn-group"> <button bindtap="clear">清空画布</button> <button bindtap="changeColor">改变画笔线条颜色</button> </view> <view class="color-preview"> <text>当前画笔:</text> <view class="color-block" style="background:{{currentColor}}"></view> <text>{{colorName}}</text> </view> </view>2.4 页面样式 index.wxss
.box { margin: 20rpx; border: 6rpx solid #9932cc; padding: 20rpx; } .title { text-align: center; font-size: 32rpx; margin-bottom:20rpx; } #myCanvas { width:100%; height:600rpx; background:#ffffff; border:1rpx solid #ccc; } .btn-group{ margin-top:20rpx; display:flex; gap:20rpx; } button{ flex:1; } .color-preview{ display:flex; align-items:center; margin-top:20rpx; gap:12rpx; } .color-block{ width:40rpx; height:40rpx; border:1rpx solid #666; }2.5 业务逻辑 index.js
Page({ data: { colorList: [ { color:"#ff0000", name:"红色"}, { color:"#00bb00", name:"绿色"}, { color:"#0066ff", name:"蓝色"}, { color:"#ff8800", name:"橙色"}, { color:"#9922dd", name:"紫色"} ], colorIndex:0, currentColor:"#ff0000", colorName:"红色" }, onReady() { const query = this.createSelectorQuery() query.select("#myCanvas") .fields({ node:true, size:true }) .exec(res=>{ const canvas = res[0].node const ctx = canvas.getContext('2d') this.ctx = ctx this.canvas = canvas // 适配设备像素比,解决画布模糊 const dpr = wx.getSystemInfoSync().pixelRatio canvas.width = res[0].width * dpr canvas.height = res[0].height * dpr ctx.scale(dpr,dpr) ctx.lineWidth = 6 ctx.lineCap = "round" ctx.strokeStyle = this.data.currentColor }) }, // 触摸开始,绘制起点 paintStart(e){ if(!this.ctx) return const x = e.touches[0].x const y = e.touches[0].y this.ctx.beginPath() this.ctx.moveTo(x,y) }, // 触摸移动,持续画线 paintMove(e){ if(!this.ctx) return const x = e.touches[0].x const y = e.touches[0].y this.ctx.lineTo(x,y) this.ctx.strokeStyle = this.data.currentColor this.ctx.stroke() }, // 清空画布 clear(){ if(!this.ctx || !this.canvas) return const w = this.canvas.width const h = this.canvas.height this.ctx.clearRect(0,0,w,h) }, // 切换画笔颜色 changeColor(){ let idx = this.data.colorIndex idx++ if(idx >= this.data.colorList.length){ idx = 0 } const item = this.data.colorList[idx] this.setData({ colorIndex: idx, currentColor: item.color, colorName: item.name }) } })三、核心问题与踩坑记录
3.1 获取 Canvas 上下文方式变更
旧版使用wx.createCanvasContext,type="2d"不再支持。 ✅正确:createSelectorQuery获取 node 节点,调用getContext('2d');代码写在onReady,不要写在onLoad,onLoad 阶段 DOM 节点未完成渲染。
3.2 移动端画布线条模糊问题
手机设备像素比 dpr 大于 1,画布尺寸没有做缩放就会发虚。 ✅解决方案:canvas 实际宽高乘以 dpr,执行ctx.scale(dpr,dpr)进行画布缩放。
3.3 修改画笔颜色,已绘制线条不会变色
Canvas 是像素绘图,已经渲染到画布上的像素无法修改。修改strokeStyle只会对后续新绘制的线条生效,历史线条保持原样,本案例就是该效果。
3.4 渲染器兼容问题
skyline 渲染器对 canvas2d 组件存在部分兼容性 bug,项目配置renderer":"webview"保证功能稳定。
四、实现效果
五、总结
- 新版
canvas type="2d"和旧版 API 完全不同,不能沿用旧的上下文获取写法,这是最容易出错的点。 - 移动端开发必须处理设备像素比 dpr,否则画布会模糊,影响用户体验。
- Canvas 是像素级渲染,已经画出来的图形无法二次修改属性,颜色、粗细修改仅作用于后续绘制。
- 渲染器的选择不容忽视,遇到莫名其妙的报错优先排查渲染器配置。
- 本案例可以直接作为电子签名、涂鸦画板的基础模板,在此基础上扩展业务功能。