#HarmonyOS NEXT体验官# 基于鸿蒙Next的App(创意工坊)解析6:如何将SVG插入到画布中 原创
本文会详细介绍如何将矢量图(SVG)插入画布,矢量图需要被编码为base64,然后直接保存到js代码中。如显示动物列表的js代码如下图所示:
每一个svg是一个数组元素。最终的SVG显示界面如下图所示。
这个界面是使用InsertImages组件实现的。。
InsertImages类实现的详细解释与注释
代码解释
这段代码实现了一个用于选择图像的组件 InsertImages
。该组件由一组按钮和一个Webview组成,按钮用于选择不同的图像类别,Webview用于显示图像列表。用户可以点击按钮从不同的图像类别中选择图像,并将选择的结果通过回调函数返回。
详细代码注释
// 从相对路径引入常量,这些常量定义了一些面板和按钮的背景颜色
import { PANEL_BACKGROUND_COLOR, PANEL_BUTTON_BACKGROUND_COLOR } from '../common/const'
// 引入ArkWeb中的webview模块
import { webview } from '@kit.ArkWeb';
// 引入常用的工具函数,如去除引号
import { trimQuotes } from '../common/common';
// 使用@Entry标记这个组件为入口组件
@Entry
// 声明一个组件InsertImages
@Component
export struct InsertImages {
// 可选的回调函数,当选择图像时触发,传递图像列表的名称和索引
imageSelected?: (imageListName: string, imageListIndex: number) => void;
// 可选的回调函数,当图像列表关闭时触发
imageListClosed?: () => void;
// 声明一个WebviewController,用于控制Webview组件
controller1: webview.WebviewController = new webview.WebviewController();
// 声明一个状态变量formatName,用于存储格式名称
@State formatName: string = '';
// build方法定义了组件的UI结构
build() {
// 创建一个Column组件,用于垂直布局
Column() {
// 创建一个Row组件,用于水平布局
Row() {
// 再次创建一个Column组件,内嵌的Column,用于放置多个按钮
Column() {
// 创建一个Row组件,用于放置选择按钮和关闭图标
Row() {
// 创建一个按钮组件,用于触发选择图像的操作
Button({ type: ButtonType.Normal }) {
// 按钮内部的文本
Text('选择')
.fontSize(16) // 设置字体大小
.fontColor('#FFFFFF') // 设置字体颜色
.width('100%') // 设置宽度为100%
.height('100%') // 设置高度为100%
.textAlign(TextAlign.Center) // 设置文本居中对齐
}
.borderRadius(5) // 设置按钮的圆角半径
.width(60) // 设置按钮的宽度
.height(30) // 设置按钮的高度
.backgroundColor('#CC0000') // 设置按钮的背景颜色
.onClick(() => { // 设置按钮点击事件
// 调用Webview中的JavaScript函数获取选中的图像数据
this.controller1.runJavaScript(`getSelectedImageData()`)
.then(data => {
if (data === '') { // 如果没有选中任何图像
// 显示一个提示对话框,告知用户未选择图像
AlertDialog.show({
message: '还没有选择图像',
alignment: DialogAlignment.Center,
primaryButton: {
value: '关闭',
action: () => {
// 对话框关闭时不执行任何操作
}
}
})
} else {
// 将返回的JSON字符串解析为数组
let parsedArray: [string, number];
try {
parsedArray = JSON.parse(data);
// 提取图像列表名称和索引
let imageListName: string = parsedArray[0];
let imageListIndex: number = parsedArray[1];
// 如果imageSelected回调函数存在,则调用该回调函数
if (this.imageSelected) {
this.imageSelected(imageListName, imageListIndex);
}
} catch (e) {
console.error("Error parsing JSON string:", e); // 捕捉解析错误并输出日志
}
}
});
})
// 创建一个关闭图像列表的图标,点击时触发关闭事件
Image($r('app.media.close')).width(20).height(20)
.margin({ left: 5, top: 0 }) // 设置图标的左边距
.onClick(() => {
if (this.imageListClosed) { // 如果imageListClosed回调函数存在,则调用该回调函数
this.imageListClosed();
}
})
}
.width('100%') // 设置行宽度为100%
.height(30) // 设置行高度为30
.margin({ bottom: 15, top: 10 }) // 设置行的上下边距
.justifyContent(FlexAlign.Center) // 设置内容居中对齐
// 在Row下创建一个Text组件,用于显示分隔线
Text() {
}
.width('100%') // 设置分隔线的宽度为100%
.height(2) // 设置分隔线的高度
.backgroundColor('#CCCCCC') // 设置分隔线的背景颜色
.margin({ bottom: 15 }) // 设置分隔线的下边距
// 以下是创建多个按钮的代码,每个按钮对应一个图像类别,点击时更新Webview中的图像列表
// 创建一个“动物”按钮
Button({ type: ButtonType.Normal }) {
Text('动物')
.fontSize(16)
.fontColor('#FFFFFF')
.width('100%')
.height('100%')
.textAlign(TextAlign.Center)
}
.borderRadius(5)
.width('100%')
.height(30)
.margin({ bottom: 10 })
.onClick(() => {
this.controller1.runJavaScript(`updateImageList("animals",5)`);
})
// 创建一个“人物”按钮
Button({ type: ButtonType.Normal }) {
Text('人物')
.fontSize(16)
.fontColor('#FFFFFF')
.width('100%')
.height('100%')
.textAlign(TextAlign.Center)
}
.borderRadius(5)
.width('100%')
.height(30)
.margin({ bottom: 10 })
.onClick(() => {
this.controller1.runJavaScript(`updateImageList("persons",5)`);
})
// 创建一个“美食”按钮
Button({ type: ButtonType.Normal }) {
Text('美食')
.fontSize(16)
.fontColor('#FFFFFF')
.width('100%')
.height('100%')
.textAlign(TextAlign.Center)
}
.borderRadius(5)
.width('100%')
.height(30)
.margin({ bottom: 10 })
.onClick(() => {
this.controller1.runJavaScript(`updateImageList("finefoods",5)`);
})
// 创建一个“水果”按钮
Button({ type: ButtonType.Normal }) {
Text('水果')
.fontSize(16)
.fontColor('#FFFFFF')
.width('100%')
.height('100%')
.textAlign(TextAlign.Center)
}
.borderRadius(5)
.width('100%')
.height(30)
.margin({ bottom: 10 })
.onClick(() => {
this.controller1.runJavaScript(`updateImageList("fruits",5)`);
})
// 创建一个“建筑”按钮
Button({ type: ButtonType.Normal }) {
Text('建筑')
.fontSize(16)
.fontColor('#FFFFFF')
.width('100%')
.height('100%')
.textAlign(TextAlign.Center)
}
.borderRadius(5)
.width('100%')
.height(30)
.margin({ bottom: 10 })
.onClick(() => {
this.controller1.runJavaScript(`updateImageList("buildings",5)`);
})
// 创建一个“交通工具”按钮
Button({ type: ButtonType.Normal }) {
Text('交通工具')
.fontSize(16)
.fontColor('#FFFFFF')
.width('100%')
.height('100%')
.textAlign(TextAlign.Center)
}
.borderRadius(5)
.width('100%')
.height(30)
.margin({ bottom: 10 })
.onClick(() => {
this.controller1.runJavaScript(`updateImageList("vehicles",5)`);
})
// 创建一个“星辰大海”按钮
Button({ type: ButtonType.Normal }) {
Text('星辰大海')
.fontSize(16)
.fontColor('#FFFFFF')
.width('100%')
.height('100%')
.textAlign(TextAlign.Center)
}
.borderRadius(5)
.width('100%')
.height(30)
.margin({ bottom: 10 })
.onClick(() => {
this.controller1.runJavaScript(`updateImageList("stars",5)`);
})
// 创建一个“植物”按钮
Button({ type: ButtonType
.Normal }) {
Text('植物')
.fontSize(16)
.fontColor('#FFFFFF')
.width('100%')
.height('100%')
.textAlign(TextAlign.Center)
}
.borderRadius(5)
.width('100%')
.height(30)
.margin({ bottom: 10 })
.onClick(() => {
this.controller1.runJavaScript(`updateImageList("plants",5)`);
})
// 创建一个“基础图形”按钮
Button({ type: ButtonType.Normal }) {
Text('基础图形')
.fontSize(16)
.fontColor('#FFFFFF')
.width('100%')
.height('100%')
.textAlign(TextAlign.Center)
}
.borderRadius(5)
.width('100%')
.height(30)
.margin({ bottom: 10 })
.onClick(() => {
this.controller1.runJavaScript(`updateImageList("basics",5)`);
})
}
.backgroundColor(PANEL_BACKGROUND_COLOR) // 设置内嵌Column的背景颜色
.border({ width: 0, color: '#000000' }) // 设置边框宽度和颜色
.height('100%') // 设置高度为100%
.padding({ left: 10, right: 10 }) // 设置左右内边距
.width(100) // 设置宽度为100
// 创建另一个Column组件用于放置Webview
Column() {
// 创建一个Webview组件,用于展示图像列表
Web({ src: $rawfile('photo_studio/system_image_list.html'), controller: this.controller1 })
.size({ width: '100%', height: '100%' }) // 设置Webview的大小为100%
}
.width('100%') // 设置宽度为100%
.height('100%') // 设置高度为100%
}
.width('100%') // 设置外部Row的宽度为100%
.height('100%') // 设置外部Row的高度为100%
}
.width('100%') // 设置最外层Column的宽度为100%
.height('100%') // 设置最外层Column的高度为100%
}
}
代码的原理和实现过程
这个组件的主要功能是提供一个界面,让用户可以从多个预定义的图像类别中选择图像,并且通过与Webview的交互来展示不同类别的图像列表。
-
组件的结构:
- 使用ArkTS的UI组件(如
Column
、Row
、Button
等)来构建界面,包含一系列按钮和一个Webview。 - 每个按钮对应不同的图像类别,点击按钮时,通过
WebviewController
来执行JavaScript代码,以更新Webview中的图像列表。
- 使用ArkTS的UI组件(如
-
事件处理:
- 使用
onClick
事件处理按钮点击,当用户点击某个类别按钮时,WebviewController
会运行对应的JavaScript函数,更新Webview中的内容。 - 通过
imageSelected
和imageListClosed
这两个可选的回调函数,组件可以在外部处理图像选择和列表关闭的逻辑。
- 使用
-
Webview交互:
WebviewController
的runJavaScript
方法用于在Webview中执行JavaScript代码,组件通过这种方式与Webview进行通信。
-
UI设计:
- 界面布局通过多个
Column
和Row
组件实现,按钮的样式和布局使用了ArkTS的样式属性(如borderRadius
、backgroundColor
、margin
等)。 - Webview组件显示图像列表的HTML页面,页面路径通过
src
属性指定。
- 界面布局通过多个
这段代码展示了如何在ArkTS中构建一个复杂的UI组件,并结合Webview进行灵活的内容展示与交互。
在选择svg时,会调用imageSelected事件函数,该函数的代码如下:
imageSelected(imageListName: string, imageListIndex: number)
{
this.controller1.runJavaScript(`addSVGImage("${imageListName}",${imageListIndex});`).then(data => {
});
this.toggleInsertImagesPanel();
}
}
在上面的代码中,调用了用js实现的addSVGImage函数,这个函数是根据svg图像的索引将svg图像插入到画布中,代码如下:
function addSVGImage(imageList, index) {
try {
// 使用fabric.loadSVGFromString来加载SVG代码
fabric.loadSVGFromString(window[imageList][index], function (objects, options) {
var svg = fabric.util.groupSVGElements(objects, options);
// 计算尺寸缩放比例以适应预定尺寸
const scale = Math.min(
150 / svg.width,
150 / svg.height
);
svg.scale(scale).set({
left: 20,
top: 20
});
// 将SVG图像添加到canvas
canvas.add(svg);
canvas.renderAll();
});
} catch(e) {
}
}
上面的代码使用了loadSVGFromString函数装载SVG图像,下面是对这个函数的详细解释。
fabric.loadSVGFromString
函数的原型和用法详解
fabric.loadSVGFromString
是 Fabric.js
库中用于从 SVG 字符串加载 SVG 图像的函数。Fabric.js 是一个强大的 JavaScript 库,用于在 HTML5 Canvas 上轻松绘制复杂的矢量图形和图片。
原型
fabric.loadSVGFromString(
svgString: string,
callback: (objects: fabric.Object[], options: object) => void,
reviver?: (element: SVGElement, object: fabric.Object) => void
): void
参数解释
-
svgString: string
- 描述: SVG 图像的字符串形式。这个字符串包含了完整的 SVG 定义,例如
<svg>
标签及其内容。 - 类型:
string
- 示例:
const svgString = ` <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"> <circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" /> </svg>`;
- 描述: SVG 图像的字符串形式。这个字符串包含了完整的 SVG 定义,例如
-
callback: (objects: fabric.Object[], options: object) => void
- 描述: 一个回调函数,在 SVG 解析完成后调用。这个回调函数接收两个参数:
objects
: Fabric.js 对象数组,每个对象代表解析后的 SVG 元素(例如,fabric.Circle
,fabric.Rect
等)。options
: 包含一些额外的选项,如 SVG 的width
和height
。
- 类型:
function
- 示例:
function callback(objects, options) { const svgGroup = new fabric.Group(objects); canvas.add(svgGroup); canvas.renderAll(); }
- 描述: 一个回调函数,在 SVG 解析完成后调用。这个回调函数接收两个参数:
-
reviver: (element: SVGElement, object: fabric.Object) => void (可选)
- 描述: 一个可选的“复活器”函数,它在每个 SVG 元素被解析成
fabric.Object
对象之前调用。通过这个函数,你可以自定义每个对象的属性或行为。 - 类型:
function
- 示例:
function reviver(element, object) { if (object.type === 'circle') { object.set({ fill: 'blue' }); } }
- 描述: 一个可选的“复活器”函数,它在每个 SVG 元素被解析成
返回值
- 返回值类型:
void
- 描述: 该函数没有返回值,而是通过回调函数来处理解析后的对象和选项。
用法示例
以下是一个完整的例子,演示如何使用 fabric.loadSVGFromString
加载 SVG 字符串,并将其添加到 Fabric.js 的画布中。
// 创建一个 Fabric.js 画布
const canvas = new fabric.Canvas('canvas');
// SVG 字符串
const svgString = `
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
</svg>`;
// 加载 SVG 字符串
fabric.loadSVGFromString(svgString, function(objects, options) {
// 创建一个组(Group),将解析出的对象添加到组中
const svgGroup = new fabric.Group(objects);
// 添加组到画布上
canvas.add(svgGroup);
// 渲染画布
canvas.renderAll();
});
详细说明
- SVG 解析:
fabric.loadSVGFromString
函数内部使用浏览器的原生 SVG 解析能力,解析传入的 SVG 字符串,并将其转化为 Fabric.js 可以理解的对象模型。 - 异步操作: 这个函数是异步的,它不会立即返回解析结果,而是在完成解析后调用提供的回调函数。这意味着在解析过程中,主线程不会被阻塞,确保应用程序的响应性。
- 对象创建: 解析后的 SVG 元素被转化为 Fabric.js 的对象,如
fabric.Circle
,fabric.Rect
,fabric.Path
等。这些对象可以像其他 Fabric.js 对象一样进行操作和管理。 - 组管理: 解析出的对象通常会被分组(使用
fabric.Group
),这样可以作为一个整体来管理。通过对组进行操作,可以轻松移动、缩放、旋转整个 SVG 图像。
使用场景
- 动态加载 SVG: 你可以从服务器或用户输入动态加载 SVG 内容,并将其显示在 Fabric.js 的画布上。
- 编辑 SVG: 解析后的 Fabric.js 对象可以直接编辑,允许你在应用程序中提供 SVG 图像的编辑功能。
- 导入外部图形: 将外部的矢量图形导入到你的 Fabric.js 画布中,以便进行进一步的操作或展示。
通过 fabric.loadSVGFromString
,你可以轻松地将 SVG 数据集成到 Fabric.js 的绘图环境中,为创建复杂、可交互的矢量图形应用提供了便利。