查看: 8243|回复: 3

鸿蒙uni-app x Canvas异步绘图与海报签名实战

[复制链接]
发表于 2026-9-22 11:00:00 | 显示全部楼层 |阅读模式
在移动端开发中,Canvas 常用于分享海报、图表、图片水印、签名板和小游戏等场景。uni-app x 在鸿蒙平台也提供了 Canvas 能力,但从获取上下文开始就不是同步流程:需要通过 uni.createCanvasContextAsync 异步拿到 CanvasContext,再调用 getContext('2d') 得到 CanvasRenderingContext2D。鸿蒙从 4.61 开始支持这一能力。

为什么异步?原文给出的原因是为了跨端兼容:微信小程序只支持异步获取 Canvas 上下文,uni-app x 因此也采用异步设计。对 HarmonyOS 开发者来说,这意味着不能依赖同步顺序初始化画布,必须在 success 回调里保存上下文并处理 context 为空的情况。

API 形态与兼容性
API 签名为:
  1. uni.createCanvasContextAsync(options: CreateCanvasContextAsyncOptions)
复制代码
调用时通过 options 指定 canvas 的 id,并设置 success、fail 回调。成功后会得到 CanvasContext,再执行:
  1. const context = ctx.getContext('2d')
  2. 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 回调中完成:
  1. const drawRectangle = () => {
  2.   uni.createCanvasContextAsync({
  3.     id: 'myCanvas',
  4.     success: (ctx) => {
  5.       const context = ctx.getContext('2d')
  6.       if (context == null) return
  7.       context.fillStyle = '#4CAF50'
  8.       context.fillRect(10, 10, 150, 100)
  9.       context.strokeStyle = '#2196F3'
  10.       context.lineWidth = 3
  11.       context.strokeRect(20, 20, 130, 80)
  12.     },
  13.     fail: (err) => {
  14.       console.error('获取 Canvas 上下文失败:', err)
  15.     }
  16.   })
  17. }
复制代码
文字绘制通过 font、fillStyle、textAlign、fillText 组合控制:
  1. context.font = '20px sans-serif'
  2. context.fillStyle = '#333333'
  3. context.textAlign = 'center'
  4. context.fillText('Hello uni-app x', 150, 50)
  5. context.font = 'bold 16px sans-serif'
  6. context.fillStyle = '#2196F3'
  7. context.textAlign = 'left'
  8. context.fillText('鸿蒙平台 Canvas 绘图', 10, 100)
复制代码
圆形、线条等路径图形则使用 beginPath、arc、moveTo、lineTo、stroke 或 fill 完成。

图片、动画与 requestAnimationFrame
绘制图片时,CanvasContext 提供 createImage 方法,设置 onload 后再 drawImage:
  1. const img = ctx.createImage()
  2. img.onload = () => {
  3.   context.drawImage(img, 0, 0, 200, 200)
  4. }
  5. img.src = '/static/logo.png'
复制代码
动画场景使用 ctx.requestAnimationFrame 驱动帧循环。原文示例中先定义 x、y、speedX、speedY,然后在每帧清空画布、绘制移动的小球,并做边界反弹:
  1. const animate = (time: number) => {
  2.   context.clearRect(0, 0, 300, 300)
  3.   context.beginPath()
  4.   context.arc(x, y, 20, 0, Math.PI * 2)
  5.   context.fillStyle = '#4CAF50'
  6.   context.fill()
  7.   x += speedX
  8.   y += speedY
  9.   if (x >= 280 || x <= 20) speedX = -speedX
  10.   if (y >= 280 || y <= 20) speedY = -speedY
  11.   ctx.requestAnimationFrame(animate)
  12. }
  13. ctx.requestAnimationFrame(animate)
复制代码
这类动画的初始化同样要放在 createCanvasContextAsync 的 success 回调内,否则拿不到有效 context。

分享海报与签名板
分享海报通常包括背景、标题、分割线、正文换行和底部信息。正文换行可用 measureText 逐字测量宽度,超过 maxWidth 就换行。绘制结束后通过 toDataURL 导出:
  1. const dataURL = ctx.toDataURL()
  2. console.log('海报生成完成,dataURL 长度:', dataURL.length)
复制代码
签名板则依赖触摸事件。初始化时设置 strokeStyle、lineWidth、lineCap、lineJoin,触摸移动时用 moveTo / lineTo / stroke 连线:
  1. canvasContext.beginPath()
  2. canvasContext.moveTo(lastX, lastY)
  3. canvasContext.lineTo(currentX, currentY)
  4. canvasContext.stroke()
  5. lastX = currentX
  6. lastY = currentY
复制代码
清空画布使用 clearRect(0, 0, 300, 300)。保存签名时同样调用 canvasCtx.toDataURL(),原文示例中提示可将结果继续上传到云存储。

页面初始化与排障建议
在完整页面中,通常先声明 canvasCtx: CanvasContext | null,再统一初始化:
  1. let canvasCtx: CanvasContext | null = null
  2. const initCanvas = () => {
  3.   uni.createCanvasContextAsync({
  4.     id: 'myCanvas',
  5.     success: (ctx) => {
  6.       canvasCtx = ctx
  7.       console.log('Canvas 初始化成功')
  8.     },
  9.     fail: (err) => {
  10.       console.error('Canvas 初始化失败:', err)
  11.     }
  12.   })
  13. }
复制代码
绘图前应判断 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 完成导出。
回复

使用道具 举报

发表于 2026-9-22 19:30:00 | 显示全部楼层

Re: 鸿蒙uni-app x Canvas异步绘图与海报签名实战

感谢分享,内容很实用,收藏了。之前容易忽略的就是鸿蒙 uni-app x 这里从 createCanvasContextAsync 开始就是异步流程,必须把 context 的获取、getContext('2d') 和判空都放在 success 回调里处理,不能按同步顺序初始化。文中提到 toBlob 在 App 端不支持、导出要用 toDataURL,这一点对做分享海报很关键。 基础绘图、文字、图片和 requestAnimationFrame 动画这几块整理得也比较清楚,尤其是动画每帧 clearRect 再重绘,并且初始化也要放进 success 回调里,这个顺序确实要注意。海报里用 measureText 做正文换行也很实用。 签名板部分好像还没写完,期待后续把触摸事件初始化和绘制流程补全,特别是它怎么和前面异步拿到的 context 配合起来。
回复 支持 反对

使用道具 举报

发表于 2026-9-22 19:40:00 | 显示全部楼层

Re: 鸿蒙uni-app x Canvas异步绘图与海报签名实战

感谢分享,这篇把鸿蒙端 uni-app x 的 Canvas 异步链路讲得挺清楚。之前很容易按 Web 那套同步思路去写,结果上下文拿不到,现在明白要从 createCanvasContextAsync 的 success 回调里拿 CanvasContext,再 getContext('2d'),而且一定要判空。toBlob 在 App 端不支持、导出要用 toDataURL 这一点也很关键,做分享海报时确实容易踩坑。 想问下签名板后续初始化,是不是同样要放在 success 回调里,先清空画布再绑定触摸事件,拿到坐标后通过 moveTo、lineTo 和 stroke 来画?动画那部分用 requestAnimationFrame 每帧 clearRect 再重绘的思路很直观,如果后面有鸿蒙端帧率或导出性能的补充就更好了。整体整理得很有帮助。
回复 支持 反对

使用道具 举报

发表于 2026-9-22 19:50:00 | 显示全部楼层

Re: 鸿蒙uni-app x Canvas异步绘图与海报签名实战

感谢分享,这篇对鸿蒙端 uni-app x 的 Canvas 流程讲得挺清楚。异步获取上下文这点确实很关键,不能按 Web 那种同步思维直接初始化,必须放在 createCanvasContextAsync 的 success 回调里,并且对 getContext('2d') 判空。toBlob 在 App 端不支持、导出用 toDataURL 这个差异也很容易踩坑,记下了。 矩形、文字、路径、图片、动画这些用法整理得比较直观,requestAnimationFrame 做帧循环和边界反弹的例子也挺实用,尤其是初始化必须放在 success 回调内这一点。签名板部分好像停在“依赖触摸事件,初始化”这里,后面如果能把 touchstart、touchmove、touchend 怎么配合 beginPath、moveTo、lineTo、stroke 来画线,以及清空和导出签名再补充一下,就更完整了。期待后续。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-10-7 00:36 , Processed in 0.030347 second(s), 17 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部