ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

微信小程序 canvas 组件实战:从自由绘图到画笔颜色切换

微信小程序 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 class="nav-bar" style="padding-top: {{statusBarHeight}}px;"> <text class="nav-title">canvas组件</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 class="scrollarea" scroll-y type="list"> <view class="page-content"> <view class="box"> <view class="title">自由绘图</view> <canvas type="2d" id="my-canvas" bind:touchstart="paintStart" bind:touchmove="paintMove" /> <view class="btn-layout"> <button type="warn" size="mini" bind:tap="clear">清空画布</button> <button type="primary" size="mini" bind:tap="changeColor">改变画笔颜色</button> </view> </view> </view> </scroll-view>

核心:<canvas type="2d">声明使用 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 作为 strokeStyle
data: { 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.wxml

<view class="nav-bar" style="padding-top: {{statusBarHeight}}px;"> <text class="nav-title">canvas组件</text> </view> <scroll-view class="scrollarea" scroll-y type="list"> <view class="page-content"> <view class="box"> <view class="title">自由绘图</view> <canvas type="2d" id="my-canvas" bind:touchstart="paintStart" bind:touchmove="paintMove" /> <view class="btn-layout"> <button type="warn" size="mini" bind:tap="clear">清空画布</button> <button type="primary" size="mini" bind:tap="changeColor">改变画笔颜色</button> </view> </view> </view> </scroll-view>

pages/index/index.wxss

page { 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.js

Page({ 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) } })

八、运行与调试要点

  1. 修改 app.json 后热重载不生效,需点击【编译】或【清缓存】+【编译】。
  2. canvas.width/height 只在 onReady 初始化时设置一次,再次赋值会清空画布。
  3. 画笔颜色存在 data.currentColor,改色只影响新笔触,旧线条像素不变。
  4. 高清屏模糊问题通过 dpr 缩放解决。

九、总结

通过案例 4.10,我掌握了:

  • Canvas 2D 绘图上下文的创建(getContext (‘2d’) + dpr 适配)
  • 基于触摸事件(touchstart /touchmove)的自由绘图
  • 清空画布与画笔颜色切换扩展
  • 关键原理:canvas 内容是固化像素,改 strokeStyle 不影响旧笔迹
返回列表