一 实现功能
- 手指触摸画布自由绘制手写线条;
- 清空画布按钮,一键清除画布所有笔迹;
- ✨作业扩展:改变画笔线条颜色按钮,循环切换画笔颜色;
- 适配设备像素比 dpr,解决真机画布线条模糊。
二 项目初始化
新建小程序项目,不使用云服务、不使用模板。
案例要求
renderer设置为webview渲染模式。
app.json
{ "pages": [ "pages/index/index" ], "window": { "navigationBarTextStyle": "black", "navigationStyle": "custom" }, "style": "v2", "renderer": "webview", "componentFramework": "glass‑easel", "sitemapLocation": "sitemap.json", "lazyCodeLoading": "requiredComponents" }1、页面布局 index.wxml
<!--index.wxml--> <scroll-view class="scrollarea" scroll-y type="list"> <view class="box"> <view class="title">自由绘图</view> <canvas type="2d" id="my‑canvas" bind:touchstart="paintStart" bind:touchmove="paintMove"></canvas> <view class="btn‑layout"> <button type="primary" size="mini" bind:tap="clear">清空画布</button> <button type="primary" size="mini" bind:tap="changePenColor">改变画笔线条颜色</button> </view> </view> </scroll-view>2、样式代码
pages/index/index.wxss
/*index.wxss*/ page { height: 100vh; display: flex; flex-direction: column; } .scrollarea { flex: 1; overflow‑y: hidden; } canvas { width: 100%; height: 800rpx; background‑color: cornflowerblue; } .btn‑layout { margin‑top: 20rpx; display: flex; justify‑content: space‑evenly; gap:20rpx; }app.wxss 全局样式
/**app.wxss**/ .box { margin: 20rpx; padding: 20rpx; border: 2rpx solid purple; } .title { font‑size: 40rpx; font‑weight: bolder; text‑align: center; margin‑bottom: 30rpx; color: red; }3、业务逻辑 index.js【核心代码】
// index.js Page({ data:{ // 画笔颜色数组,循环切换 colorList:["#ff0000","#00ff00","#0000ff","#ffff00"], colorIndex:0 }, onReady() { const query = wx.createSelectorQuery() query.select('#my‑canvas') .fields({ node: true, size: true }) .exec((res) => { const canvas = res[0].node const ctx = canvas.getContext('2d') // 获取设备像素比 const dpr = wx.getSystemInfoSync().pixelRatio // 设置画布实际像素宽高 canvas.width = res[0].width * dpr canvas.height = res[0].height * dpr // 坐标缩放适配设备像素 ctx.scale(dpr, dpr) this.canvas = canvas this.ctx = ctx }) }, // 触摸开始 paintStart(e) { let x = e.touches[0].x let y = e.touches[0].y let ctx = this.ctx // 获取当前选中画笔颜色 let currentColor = this.data.colorList[this.data.colorIndex] ctx.strokeStyle = currentColor ctx.lineCap = 'round' ctx.lineJoin = 'round' ctx.lineWidth = 5 ctx.moveTo(x, y) }, // 触摸移动,绘制线条 paintMove(e) { let x = e.touches[0].x let y = e.touches[0].y let ctx = this.ctx let currentColor = this.data.colorList[this.data.colorIndex] ctx.strokeStyle = currentColor ctx.lineTo(x, y) ctx.stroke() }, // 清空画布 clear() { let canvas = this.canvas let ctx = this.ctx ctx.beginPath() ctx.clearRect(0, 0, canvas.width, canvas.height) }, //作业扩展:切换画笔颜色 changePenColor(){ let idx = this.data.colorIndex idx = idx +1 if(idx >= this.data.colorList.length){ idx =0 } this.setData({ colorIndex: idx }) wx.showToast({ title:`已切换画笔颜色`, icon:"none", duration:800 }) } })三 运行效果说明
- 页面初始化:蓝色背景画布,默认画笔红色,手指滑动就可以涂鸦写字;
- 清空画布按钮:点击画布全部清除,回到空白画板;
- 改变画笔线条颜色按钮:点击弹出提示,之后新绘制线条切换颜色(红→绿→蓝→黄循环);已经画好的笔迹不会变色,属于 Canvas2D 特性;
- 清空画布之后,继续绘画使用已经切换后的画笔颜色,和课程作业截图效果完全一致。
四 核心知识点总结
1.canvas 2D 组件
新版<canvas type="2d">不再使用旧版canvas‑id,通过SelectorQuery拿到原生 node 对象,调用getContext('2d')获取绘图上下文。
2. 绘图常用 API
表格
| API | 作用 |
|---|---|
moveTo(x,y) | 将画笔移动到起始坐标 |
lineTo(x,y) | 绘制直线到目标坐标 |
stroke() | 执行描边,真正绘制线条 |
lineCap:'round' | 设置线条端点圆润 |
lineJoin:'round' | 设置线条拐角圆润 |
clearRect(x,y,w,h) | 清空指定矩形画布区域 |
beginPath() | 开启全新绘图路径 |
3. 设备像素比 dpr
手机屏幕物理像素远大于 css 像素,画布宽高需要乘以 dpr,同时执行ctx.scale(dpr,dpr),否则真机绘制线条模糊。
4. 切换画笔颜色逻辑
维护颜色数组,点击按钮修改颜色索引;必须在paintStart、paintMove事件内部重新赋值strokeStyle,否则绘制的时候颜色会失效。
⚠注意:Canvas 绘制完成之后像素直接渲染到画布,不会保存绘图对象,旧笔迹不能直接修改颜色;如果想要修改旧笔迹颜色,需要自己保存全部笔画点位,清空画布再全部重绘。
⚠踩坑记录
- 真机线条模糊:忘记设置
canvas.width = res[0].width*dpr,没有执行 scale 缩放; - 清空画布残留线条:清空画布前没有调用
beginPath()开启新路径; - 切换颜色失效:仅仅在 changePenColor 修改 strokeStyle,没有在触摸事件重新赋值;
- canvas 属于原生组件,部分 css 样式会有兼容性限制。