在鸿蒙元服务里,Canvas 常被用于海报生成、签名板、数据可视化和图片编辑。ASCF 提供的画布 API 通过 CanvasContext 使用,方法基本覆盖 Canvas 2D 常用能力。下面按创建、绘制、样式、变换、像素、导出和排障几个环节,把实际开发中会用到的调用方式串起来。
创建画布与上下文
HXML 中先声明 canvas,canvas-id 要和 createCanvasContext 的参数一致:
- <canvas canvas-id='myCanvas' style='width: 300px; height: 200px;' />
复制代码
在页面里创建上下文:
- const ctx = has.createCanvasContext('myCanvas');
复制代码
如果在自定义组件中使用,需要把组件实例作为第二个参数传入:
- const ctx = has.createCanvasContext('myCanvas', this);
复制代码
基础绘制
矩形使用 fillRect 和 strokeRect,绘制完成后调用 draw:
- ctx.setFillStyle('#0A59F7');
- ctx.fillRect(30, 30, 200, 100);
- ctx.setStrokeStyle('#ff0000');
- ctx.setLineWidth(2);
- ctx.strokeRect(30, 30, 200, 100);
- ctx.draw();
复制代码
线段通过 moveTo、lineTo、stroke 组合:
- ctx.beginPath();
- ctx.moveTo(10, 10);
- ctx.lineTo(200, 100);
- ctx.setStrokeStyle('#333333');
- ctx.setLineWidth(3);
- ctx.stroke();
- ctx.draw();
复制代码
圆弧用 arc(x, y, r, sAngle, eAngle, counterclockwise)。sAngle 为起始弧度,0 是 3 点钟方向,2 * Math.PI 是一整圈,counterclockwise 默认 false 表示顺时针。arcTo 则根据控制点和半径画弧线。
文字用 fillText 或 strokeText,setTextAlign 支持 left/center/right;图片用 drawImage。drawImage 只接收本地路径或下载后的临时路径,网络图片要先通过 has.downloadFile 下载,再把 res.tempFilePath 传给 drawImage。
路径、曲线与样式
beginPath 开始新路径,closePath 连接当前点和起点形成封闭图形。多段路径可以分别 beginPath、设置样式、再 stroke,互不影响。曲线常用 quadraticCurveTo 和 bezierCurveTo,前者一个控制点,后者两个控制点。
样式包括 setFillStyle、setStrokeStyle、setLineWidth、setLineCap、setLineJoin、setLineDash、setMiterLimit、setShadow、setGlobalAlpha。填充和描边也支持渐变对象,线性渐变用 createLinearGradient,径向渐变用 createCircularGradient,再通过 addColorStop 添加色标。
坐标变换与剪切
translate、rotate、scale 都是坐标变换。rotate 参数是弧度,不是角度。变换会累积,推荐用 save/restore 成对包住,避免影响后续绘制。剪切用 clip,剪切后所有绘制都限制在剪切区域内,圆形头像裁剪就是典型用法:save、beginPath、arc、clip、drawImage、restore。
像素操作
像素读写分别用 has.canvasGetImageData 和 has.canvasPutImageData。读取结果里的 data 是 Uint8ClampedArray,每 4 个值表示一个像素的 RGBA。写入时需要按 RGBA 填充一维数组,再传给 canvasPutImageData。
导出图片
导出用 has.canvasToTempFilePath,但要特别注意:它必须放在 draw 的回调里执行,否则可能导出空白或旧内容。draw 本身是异步的,绘制 API 是同步的,所以在 draw 完成后再执行导出才可靠。
- ctx.draw(false, () => {
- has.canvasToTempFilePath({
- canvasId: 'myCanvas',
- x: 0,
- y: 0,
- width: 300,
- height: 200,
- destWidth: 600,
- destHeight: 400,
- fileType: 'png',
- quality: 1,
- success: (res) => {
- console.info('图片路径:', res.tempFilePath);
- has.saveImageToPhotosAlbum({
- filePath: res.tempFilePath
- });
- }
- });
- });
复制代码
canvasToTempFilePath 支持 x、y、width、height,以及输出尺寸 destWidth、destHeight,fileType 可选 png 或 jpg,quality 用于 jpg 质量。导出成功后可以继续保存到相册。
三个常见场景
海报生成:创建上下文后画背景、标题、价格、分割线,再下载商品图,drawImage 后通过 draw 回调导出并保存。
签名板:onTouchStart 记录起点,onTouchMove 里不断 beginPath、moveTo、lineTo、stroke,并且必须用 draw(true) 保留之前笔迹,否则每次移动都会清空画布。清空画布时调用 draw() 或 draw(false)。
圆形头像:用 arc 建立圆形路径并 clip,再 drawImage,最后 restore,可再描一圈白色边框。
常见坑与排查
第一,canvasToTempFilePath 不在 draw 回调里,导出可能空白。正确做法是 ctx.draw(false, () => { ...导出... })。
第二,draw(true) 和 draw(false) 行为不同。draw() 或 draw(false) 会清空画布再绘制,draw(true) 会保留之前内容并叠加。签名板、连续轨迹类场景要用 true。
第三,drawImage 不支持网络 URL。网络图片先 has.downloadFile,成功后再用 res.tempFilePath。
第四,translate、rotate、scale 会累积。没有 save/restore 时,后续绘制可能被意外变换。建议成对使用。
第五,canvas 组件的 CSS 尺寸和绘图坐标是独立体系。如果 HXML 尺寸和绘制坐标不一致,内容可能被拉伸或压缩。最好设置固定尺寸,绘图时使用相同尺寸。
第六,渐变坐标是相对 canvas 左上角,不是相对当前绘制区域。createLinearGradient 和 createCircularGradient 的坐标要按画布整体来算。
最后
Canvas API 方法很多,常用组合其实集中在几组:fillRect/strokeRect 画矩形,beginPath + lineTo + stroke 画线,arc 画圆,drawImage 画图片,draw + canvasToTempFilePath 导出图片。记住导出必须放在 draw 回调、签名板用 draw(true)、网络图片先下载、变换用 save/restore,基本能避开主要问题。 |