微信小程序 canvas 组件实战:从自由绘图到画笔颜色切换
一、引言canvas画布是微信小程序中用于自由绘图、图表展示、游戏渲染的核心组件。通过 canvas 2D 绘图上下文开发者可以像使用浏览器 Canvas API 一样在画布上绘制线条、图形、文字等。本文以「案例 4.10 canvas 组件」为主线带你完成一个可在画布上自由绘图的小程序并在此基础上完成作业扩展增加一个可以改变画笔线条颜色的按钮。二、项目结构小程序项目根目录 ├── app.json 全局配置 ├── app.wxss 全局样式 └── pages └── index ├── index.json 页面配置 ├── index.wxml 页面结构 ├── index.wxss 页面样式 └── index.js 页面逻辑三、页面布局设计1. 自定义顶部导航栏为保证任何机型下标题稳定显示采用自定义固定导航栏view classnav-bar stylepadding-top: {{statusBarHeight}}px; text classnav-titlecanvas组件/text /view.nav-bar { position: fixed; top: 0; left: 0; right: 0; height: 88rpx; background: #ffffff; display: flex; align-items: center; justify-content: center; box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08); z-index: 999; }padding-top 用状态栏高度statusBarHeight撑开避开手机顶部状态栏区域。2. 画布与按钮区scroll-view classscrollarea scroll-y typelist view classpage-content view classbox view classtitle自由绘图/view canvas type2d idmy-canvas bind:touchstartpaintStart bind:touchmovepaintMove / view classbtn-layout button typewarn sizemini bind:tapclear清空画布/button button typeprimary sizemini bind:tapchangeColor改变画笔颜色/button /view /view /view /scroll-view核心canvas type2d声明使用 Canvas 2D 接口bind:touchstart/bind:touchmove负责捕获手指按下与滑动用于绘图。四、Canvas 2D 绘图上下文创建核心微信小程序 Canvas 2D 接口与浏览器标准 API 一致需要通过选择器拿到 canvas 原生节点再调用getContext(2d)获取绘图上下文onReady() { const query wx.createSelectorQuery() query.select(#my-canvas) .fields({ node: true, size: true }) .exec((res) { const canvas res[0].node // canvas 原生对象 const ctx canvas.getContext(2d) // 2d 绘图上下文 const dpr wx.getWindowInfo().pixelRatio // 设备像素比 // 设置画布宽高适配高清屏 canvas.width res[0].width * dpr canvas.height res[0].height * dpr // 坐标系缩放保证绘制清晰 ctx.scale(dpr, dpr) this.canvas canvas this.ctx ctx }) }关键点fields({ node: true, size: true })同时获取节点实例和尺寸canvas.width / height用逻辑尺寸 × dpr避免高清屏模糊ctx.scale(dpr, dpr)让坐标单位与逻辑像素一致canvas.width 赋值会清空画布因此只能初始化时执行一次五、自由绘图实现1. 手指按下 —— 起点paintStart(e) { const x e.touches[0].x const y e.touches[0].y const ctx this.ctx ctx.strokeStyle this.data.currentColor // 线条颜色 ctx.lineCap round // 端点圆角 ctx.lineJoin round // 连接点圆角 ctx.lineWidth 5 // 线宽 ctx.moveTo(x, y) // 起点 }2. 手指滑动 —— 连续绘制paintMove(e) { const x e.touches[0].x const y e.touches[0].y const ctx this.ctx ctx.strokeStyle this.data.currentColor ctx.lineTo(x, y) // 连线到新位置 ctx.stroke() // 绘制路径 }moveTo 定起点lineTo stroke 持续画线实现流畅的自由手绘。3. 清空画布clear() { const canvas this.canvas const ctx this.ctx ctx.beginPath() // 开始新路径 ctx.clearRect(0, 0, canvas.width, canvas.height) // 清空矩形区域 }六、作业扩展改变画笔颜色作业要求在案例基础上增加一个「改变画笔颜色」的按钮且只影响接下来新画的线条已画内容颜色不变。实现思路用 data 维护一个颜色列表 colors 和当前颜色 currentColor点击按钮在颜色列表中循环切换 currentColor绘图时读取 currentColor 作为 strokeStyledata: { colors: [#ff0000, #0000ff, #00ff00, #ff9900, #9900ff, #000000], currentColor: #ff0000 // 刚开始画笔的颜色红色 }, changeColor() { const colors this.data.colors const cur this.data.currentColor const idx colors.indexOf(cur) const next colors[(idx 1) % colors.length] // 取下一个循环 this.setData({ currentColor: next }) }关键原理canvas 已画内容是固化像素改 strokeStyle 只决定新笔触的颜色不会回头修改旧线条。因此只要不重设 canvas.width、不重绘画布旧线条颜色就保持原样。七、完整代码清单app.json{ pages: [pages/index/index], window: { navigationBarTextStyle: black, navigationStyle: custom }, style: v2, renderer: webview, sitemapLocation: sitemap.json }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; }pages/index/index.wxmlview classnav-bar stylepadding-top: {{statusBarHeight}}px; text classnav-titlecanvas组件/text /view scroll-view classscrollarea scroll-y typelist view classpage-content view classbox view classtitle自由绘图/view canvas type2d idmy-canvas bind:touchstartpaintStart bind:touchmovepaintMove / view classbtn-layout button typewarn sizemini bind:tapclear清空画布/button button typeprimary sizemini bind:tapchangeColor改变画笔颜色/button /view /view /view /scroll-viewpages/index/index.wxsspage { height: 100vh; display: flex; flex-direction: column; } .nav-bar { position: fixed; top: 0; left: 0; right: 0; height: 88rpx; background: #ffffff; display: flex; align-items: center; justify-content: center; box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08); z-index: 999; } .nav-title { font-size: 34rpx; color: #000000; font-weight: 500; } .scrollarea { flex: 1; overflow-y: hidden; } .page-content { padding-top: 260rpx; } canvas { width: 100%; height: 800rpx; background-color: cornflowerblue; } .btn-layout { margin-top: 20rpx; display: flex; justify-content: center; } .btn-layout button { margin: 0 10rpx; }pages/index/index.jsPage({ data: { statusBarHeight: 20, colors: [#ff0000, #0000ff, #00ff00, #ff9900, #9900ff, #000000], currentColor: #ff0000 }, onLoad() { const info wx.getWindowInfo() this.setData({ statusBarHeight: info.statusBarHeight }) }, 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.getWindowInfo().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) { const x e.touches[0].x const y e.touches[0].y const ctx this.ctx ctx.strokeStyle this.data.currentColor ctx.lineCap round ctx.lineJoin round ctx.lineWidth 5 ctx.moveTo(x, y) }, paintMove(e) { const x e.touches[0].x const y e.touches[0].y const ctx this.ctx ctx.strokeStyle this.data.currentColor ctx.lineTo(x, y) ctx.stroke() }, changeColor() { const colors this.data.colors const cur this.data.currentColor const idx colors.indexOf(cur) const next colors[(idx 1) % colors.length] this.setData({ currentColor: next }) }, clear() { const canvas this.canvas const ctx this.ctx ctx.beginPath() ctx.clearRect(0, 0, canvas.width, canvas.height) } })八、运行与调试要点修改 app.json 后热重载不生效需点击【编译】或【清缓存】【编译】。canvas.width/height 只在 onReady 初始化时设置一次再次赋值会清空画布。画笔颜色存在 data.currentColor改色只影响新笔触旧线条像素不变。高清屏模糊问题通过 dpr 缩放解决。九、总结通过案例 4.10我掌握了Canvas 2D 绘图上下文的创建getContext (‘2d’) dpr 适配基于触摸事件touchstart /touchmove的自由绘图清空画布与画笔颜色切换扩展关键原理canvas 内容是固化像素改 strokeStyle 不影响旧笔迹