在移动端开发中,Canvas 常用于分享海报、图表、图片水印、签名板和小游戏等场景。uni-app x 在鸿蒙平台也提供了 Canvas 能力,但从获取上下文开始就不是同步流程:需要通过 uni.createCanvasContextAsync 异步拿到 CanvasContext,再调用 getContext('2d') 得到 CanvasRenderingContext2D。鸿蒙从 4.61 开始支持这一能力。
为什么异步?原文给出的原因是为了跨端兼容:微信小程序只支持异步获取 Canvas 上下文,uni-app x 因此也采用异步设计。对 HarmonyOS 开发者来说,这意味着不能依赖同步顺序初始化画布,必须在 success 回调里保存上下文并处理 context 为空的情况。
API 形态与兼容性
API 签名为:- uni.createCanvasContextAsync(options: CreateCanvasContextAsyncOptions)
复制代码 调用时通过 options 指定 canvas 的 id,并设置 success、fail 回调。成功后会得到 CanvasContext,再执行:- const context = ctx.getContext('2d')
- if (context == null) return
复制代码 之后即可使用标准 Canvas 2D API 绘图。需要注意:toBlob 在 App 端(Android、iOS、HarmonyOS)都不支持。原文中的导出方案使用的是 ctx.toDataURL(),这也是鸿蒙端处理导出时应重点确认的差异点。
CanvasRenderingContext2D 可用能力
拿到 CanvasRenderingContext2D 后,可以覆盖常见绘图需求:
绘制矩形:fillRect、strokeRect、clearRect。
绘制路径:beginPath、moveTo、lineTo、arc、closePath。
绘制文本:fillText、strokeText。
绘制图片:drawImage。
样式设置:fillStyle、strokeStyle、lineWidth、font、textAlign。
变换操作:translate、rotate、scale。
状态保存:save、restore。
这些 API 与 Web Canvas 2D 的使用方式基本一致,但上下文的获取和导出接口需要按 uni-app x 在鸿蒙端的约束处理。
基础绘图:矩形与文字
最基础的填充、描边矩形可直接在 success 回调中完成:- const drawRectangle = () => {
- uni.createCanvasContextAsync({
- id: 'myCanvas',
- success: (ctx) => {
- const context = ctx.getContext('2d')
- if (context == null) return
- context.fillStyle = '#4CAF50'
- context.fillRect(10, 10, 150, 100)
- context.strokeStyle = '#2196F3'
- context.lineWidth = 3
- context.strokeRect(20, 20, 130, 80)
- },
- fail: (err) => {
- console.error('获取 Canvas 上下文失败:', err)
- }
- })
- }
复制代码 文字绘制通过 font、fillStyle、textAlign、fillText 组合控制:- context.font = '20px sans-serif'
- context.fillStyle = '#333333'
- context.textAlign = 'center'
- context.fillText('Hello uni-app x', 150, 50)
- context.font = 'bold 16px sans-serif'
- context.fillStyle = '#2196F3'
- context.textAlign = 'left'
- context.fillText('鸿蒙平台 Canvas 绘图', 10, 100)
复制代码 圆形、线条等路径图形则使用 beginPath、arc、moveTo、lineTo、stroke 或 fill 完成。
图片、动画与 requestAnimationFrame
绘制图片时,CanvasContext 提供 createImage 方法,设置 onload 后再 drawImage:- const img = ctx.createImage()
- img.onload = () => {
- context.drawImage(img, 0, 0, 200, 200)
- }
- img.src = '/static/logo.png'
复制代码 动画场景使用 ctx.requestAnimationFrame 驱动帧循环。原文示例中先定义 x、y、speedX、speedY,然后在每帧清空画布、绘制移动的小球,并做边界反弹:- const animate = (time: number) => {
- context.clearRect(0, 0, 300, 300)
- context.beginPath()
- context.arc(x, y, 20, 0, Math.PI * 2)
- context.fillStyle = '#4CAF50'
- context.fill()
- x += speedX
- y += speedY
- if (x >= 280 || x <= 20) speedX = -speedX
- if (y >= 280 || y <= 20) speedY = -speedY
- ctx.requestAnimationFrame(animate)
- }
- ctx.requestAnimationFrame(animate)
复制代码 这类动画的初始化同样要放在 createCanvasContextAsync 的 success 回调内,否则拿不到有效 context。
分享海报与签名板
分享海报通常包括背景、标题、分割线、正文换行和底部信息。正文换行可用 measureText 逐字测量宽度,超过 maxWidth 就换行。绘制结束后通过 toDataURL 导出:- const dataURL = ctx.toDataURL()
- console.log('海报生成完成,dataURL 长度:', dataURL.length)
复制代码 签名板则依赖触摸事件。初始化时设置 strokeStyle、lineWidth、lineCap、lineJoin,触摸移动时用 moveTo / lineTo / stroke 连线:- canvasContext.beginPath()
- canvasContext.moveTo(lastX, lastY)
- canvasContext.lineTo(currentX, currentY)
- canvasContext.stroke()
- lastX = currentX
- lastY = currentY
复制代码 清空画布使用 clearRect(0, 0, 300, 300)。保存签名时同样调用 canvasCtx.toDataURL(),原文示例中提示可将结果继续上传到云存储。
页面初始化与排障建议
在完整页面中,通常先声明 canvasCtx: CanvasContext | null,再统一初始化:- let canvasCtx: CanvasContext | null = null
- const initCanvas = () => {
- uni.createCanvasContextAsync({
- id: 'myCanvas',
- success: (ctx) => {
- canvasCtx = ctx
- console.log('Canvas 初始化成功')
- },
- fail: (err) => {
- console.error('Canvas 初始化失败:', err)
- }
- })
- }
复制代码 绘图前应判断 canvasCtx 是否为 null,并对 getContext('2d') 的返回值做空判断。若出现绘制无效果,优先检查 canvas id 是否与 options 中一致、success 是否已经触发、context 是否为空。若导出失败,先确认是否误用了 toBlob;在 Android、iOS、HarmonyOS 的 App 端,toBlob 均不支持,应改用原文示例中的 toDataURL 流程。
总体来看,鸿蒙 4.61 起 uni-app x 对 createCanvasContextAsync 的支持,让 Canvas 绘图、动画、海报和签名板等场景可以跨端落地。关键适配点是异步获取上下文、通过 getContext('2d') 进入标准 2D 绘图、避开 App 端不支持的 toBlob,并用 toDataURL 完成导出。 |