查看: 1290|回复: 3

鸿蒙平台uni-app x canvas高清适配与常见踩坑实战总结

[复制链接]
发表于 昨天 14:00 | 显示全部楼层 |阅读模式
在 HarmonyOS 上使用 uni-app x 开发时,只要涉及自定义图形、文字绘制,就离不开 canvas 组件。HarmonyOS 4.61 开始支持该组件。canvas 本身不显示内容,它是一个空白画布,真正绘制靠 JavaScript 调用 CanvasRenderingContext2D API。

先明确选型:uni-app x 有两套绘制方案。复杂绘制、动画、导出图片选 canvas;只画矩形、写文字,DrawableContext 更轻量。本文聚焦 canvas 基础绘制、上下文获取、高清屏适配和鸿蒙平台踩坑。

一、组件与上下文获取
基础声明:
  1. <template>
  2.   <canvas id="myCanvas" style="width: 300px; height: 200px;"></canvas>
  3. </template>
复制代码

获取上下文有异步和同步两种。跨平台推荐异步,HBuilderX 4.25+ 支持;同步仅支持 App 和 Web,HBuilderX 4.21+ 支持,小程序不支持,跨平台项目不要用。异步示例:
  1. <script setup lang="uts">
  2. onReady(() => {
  3.   uni.createCanvasContextAsync({
  4.     id: 'myCanvas',
  5.     component: getCurrentInstance()!.proxy,
  6.     success: (context: CanvasContext) => {
  7.       const ctx = context.getContext('2d')!
  8.       const canvas = ctx.canvas
  9.       const dpr = uni.getDeviceInfo().devicePixelRatio ?? 1
  10.       canvas.width = canvas.offsetWidth * dpr
  11.       canvas.height = canvas.offsetHeight * dpr
  12.       ctx.scale(dpr, dpr)
  13.       ctx.fillStyle = '#2196F3'
  14.       ctx.fillRect(10, 10, 100, 100)
  15.     }
  16.   })
  17. })
  18. </script>
复制代码

注意:组合式写法中 component 参数用 getCurrentInstance()!.proxy,选项式写法用 this。

二、高清屏适配必须做
如果不处理 dpr,高分屏上会模糊。处理方式:
  1. const dpr = uni.getDeviceInfo().devicePixelRatio ?? 1
  2. canvas.width = canvas.offsetWidth * dpr
  3. canvas.height = canvas.offsetHeight * dpr
  4. ctx.scale(dpr, dpr)
复制代码
原理是把 canvas 实际像素放大 dpr 倍,再通过 scale 缩放回逻辑尺寸。注意 ctx.reset() 会重置所有状态,包括 scale;reset 后要重新调用 scale(dpr, dpr),否则又会变模糊。

三、基础绘制场景
1. 矩形:fillRect 填充,strokeRect 描边,roundRect 圆角。roundRect 最后参数可传数字,也可传数组分别设置四个角。
  1. ctx.fillStyle = '#2196F3'
  2. ctx.fillRect(10, 10, 120, 80)
  3. ctx.strokeStyle = '#F44336'
  4. ctx.lineWidth = 3
  5. ctx.strokeRect(150, 10, 120, 80)
  6. ctx.fillStyle = '#4CAF50'
  7. ctx.beginPath()
  8. ctx.roundRect(10, 110, 120, 80, 10)
  9. ctx.fill()
复制代码

2. 圆形:arc(x, y, r, start, end)。Math.PI * 2 是完整圆,Math.PI 是半圆。角度用弧度不是角度。
  1. ctx.beginPath()
  2. ctx.arc(60, 60, 50, 0, Math.PI * 2)
  3. ctx.fill()
复制代码

3. 文字:fillText 填充文字,strokeText 描边文字,measureText 测量宽度。measureText 常用来计算标签或徽章背景尺寸。
  1. ctx.font = '20px sans-serif'
  2. ctx.fillStyle = '#333333'
  3. ctx.fillText('Hello Canvas!', 10, 30)
  4. const metrics = ctx.measureText('带背景的文字')
  5. ctx.fillStyle = '#FF9800'
  6. ctx.fillRect(10, 130, metrics.width + 10, 24)
复制代码

4. 线条与虚线:setLineDash 设置虚线,传空数组恢复实线。
  1. ctx.strokeStyle = '#F44336'
  2. ctx.setLineDash([5, 5])
  3. ctx.beginPath()
  4. ctx.moveTo(10, 70)
  5. ctx.lineTo(250, 70)
  6. ctx.stroke()
  7. ctx.setLineDash([])
复制代码

四、常用 CanvasRenderingContext2D API 速查
矩形:fillRect、strokeRect、clearRect、roundRect。
路径:beginPath、closePath、moveTo、lineTo、arc、quadraticCurveTo、bezierCurveTo、fill、stroke、clip。
文字:fillText、strokeText、measureText。
样式:fillStyle、strokeStyle、lineWidth、lineCap、lineJoin、font、textAlign、textBaseline、globalAlpha。
路径画完后,fill 填充内部,stroke 描边轮廓,两个可以同时用。

五、完整页面组织思路
一个基础绘制页面通常包括:scroll-view 中放 canvas、操作按钮和信息文本。按钮切换矩形、圆形、文字、线条、清空。脚本中缓存 renderCtx 和 canvasEl,onReady 后异步创建上下文并完成 dpr 处理,再调用 drawRect。核心初始化:
  1. let renderCtx: CanvasRenderingContext2D | null = null
  2. let canvasEl: UniCanvasElement | null = null
  3. const infoText = ref('点击按钮切换绘制内容')
  4. onReady(() => {
  5.   uni.createCanvasContextAsync({
  6.     id: 'basicCanvas',
  7.     component: getCurrentInstance()!.proxy,
  8.     success: (context: CanvasContext) => {
  9.       renderCtx = context.getContext('2d')!
  10.       canvasEl = renderCtx!.canvas
  11.       const dpr = uni.getDeviceInfo().devicePixelRatio ?? 1
  12.       canvasEl!.width = canvasEl!.offsetWidth * dpr
  13.       canvasEl!.height = canvasEl!.offsetHeight * dpr
  14.       renderCtx!.scale(dpr, dpr)
  15.       drawRect()
  16.     }
  17.   })
  18. })
复制代码

切换绘制前用 clearRect 清空,例如:
  1. const clearCanvas = () => {
  2.   if (renderCtx == null || canvasEl == null) return
  3.   renderCtx!.clearRect(0, 0, canvasEl!.offsetWidth, canvasEl!.offsetHeight)
  4.   infoText.value = '画布已清空'
  5. }
复制代码

六、鸿蒙平台特性
HarmonyOS 4.61 版本开始支持 canvas 组件。鸿蒙平台的 canvas 封装自鸿蒙自身 canvas 组件,性能表现良好;Android 默认开启硬件加速,鸿蒙也是。canvas 是独立模块,在鸿蒙平台占用几百 K 体积;如果不使用会被摇树摇掉,不会增加包体积。
与鸿蒙原生开发对比,原生写法类似:
  1. Canvas(this.context)
  2.   .width('100%')
  3.   .height(200)
  4.   .onReady(() => {
  5.     this.context.fillStyle = '#2196F3'
  6.     this.context.fillRect(10, 10, 100, 100)
  7.   })
复制代码
uni-app x 的 canvas 使用 W3C 标准语法,Web 开发者更容易上手;鸿蒙原生 Canvas API 与 W3C 存在差异,需要适应。

七、踩坑清单
1. 必须在 onReady 后获取上下文,在 onLoad 或 onMount 中获取会失败。
2. 必须处理高清屏,否则高分屏模糊。
3. 同步方式 uni.getElementById 只支持 App 和 Web,不支持小程序,跨平台统一用 createCanvasContextAsync。
4. clearRect 只清除像素,不影响 fillStyle、strokeStyle 等样式;需要重置样式要手动设置。
5. 每次画新形状前调用 beginPath,否则新形状可能继承上一个路径样式。
6. 使用 ctx.reset 后,scale 会被重置,要重新执行 scale(dpr, dpr)。

总结
在鸿蒙平台用 uni-app x 画 canvas,推荐流程是:onReady 后调用 createCanvasContextAsync,getContext('2d') 拿到上下文,按 dpr 放大实际像素并 scale 回逻辑尺寸,然后按矩形、圆形、文字、线条等场景调用 CanvasRenderingContext2D API。简单绘制可选 DrawableContext,复杂绘制、动画和导出图片再上 canvas。HarmonyOS 4.61 起已支持该组件,且未使用时可被摇树,不必担心包体积。
回复

使用道具 举报

发表于 昨天 19:00 | 显示全部楼层

Re: 鸿蒙平台uni-app x canvas高清适配与常见踩坑实战总结

感谢分享,这篇整理得很实用。高清屏适配这块总结得很清楚,dpr 放大 canvas 实际像素再 scale 回逻辑尺寸,确实是必须做的,不然高分屏文字和线条很容易发虚。ctx.reset() 会连 scale 一起清掉这个坑很关键,很多人 reset 完就忘了重新 scale。异步获取上下文跨平台更稳这点也认同,小程序不支持同步,统一走异步能少很多兼容问题。选型那块也讲得明白,复杂绘制、动画、导出图片用 canvas,只是矩形和文字用 DrawableContext 更轻量。roundRect 的四角参数、measureText 算背景宽度、setLineDash 空数组恢复实线这些细节都很适合实战。完整页面里 onReady 后异步创建上下文并缓存 renderCtx 和 canvasEl 的思路也很顺,后面做鸿蒙端 canvas 就按这个清单核对。
回复 支持 反对

使用道具 举报

发表于 昨天 19:10 | 显示全部楼层

Re: 鸿蒙平台uni-app x canvas高清适配与常见踩坑实战总结

感谢分享,这篇总结挺实在的。HarmonyOS 上用 uni-app x 画 canvas,dpr 适配确实是绕不过去的点,先按实际像素放大再 scale 回逻辑尺寸,这个思路很清楚,尤其提醒 reset 之后 scale 会被重置、必须重新调用,这个坑很容易忽略。异步获取上下文跨平台推荐也很有价值,同步接口小程序不支持这点对跨平台项目很关键。选型上复杂绘制、动画、导出图片用 canvas,简单矩形文字用 DrawableContext,边界说得明白。矩形、圆、文字、measureText 算背景宽度、虚线这些示例也都很实用。另外正文最后好像断在 let canvasEl 了,后面完整页面组织那块如果补全就更好了。
回复 支持 反对

使用道具 举报

发表于 昨天 19:20 | 显示全部楼层

Re: 鸿蒙平台uni-app x canvas高清适配与常见踩坑实战总结

楼主整理得很细,尤其高清适配这块很实用。以前只知道 canvas 模糊要乘 dpr,但没注意到 reset 会把 scale 一起重置,这个坑确实容易踩,清空后重绘一不小心又糊了。异步 createCanvasContextAsync 配 getCurrentInstance()!.proxy 的写法也记下了,跨平台项目确实要避开同步获取。选型那段也有帮助,简单矩形和文字用 DrawableContext,复杂绘制、动画和导出再上 canvas,思路更清楚。想问下,初始化时按 dpr 设好 canvas.width、height 并 scale 后,后面只是 clearRect 或重新绘制,不调用 reset 的话就不用再 scale 吧?另外首帖最后的 canvasEl 部分好像没贴完,期待完整页面组织思路。
回复 支持 反对

使用道具 举报

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

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

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

官方核心成员

关注微信公众号

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

GMT+8, 2026-9-17 17:52 , Processed in 0.021502 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部