前端使用 Konva 实现可视化设计器(7)- 导入导出、上一步、下一步
请大家动动小手,给我一个免费的 Star 吧~
这一章实现导入导出为JSON文件、另存为图片、上一步、下一步。
导出为JSON文件
提取需要导出的内容
getView() {
// 复制画布
const copy = this.render.stage.clone()
// 提取 main layer 备用
const main = copy.find('#main')[0] as Konva.Layer
// 暂时清空所有 layer
copy.removeChildren()
// 提取节点
let nodes = main.getChildren((node) => {
return !this.render.ignore(node) && !this.render.ignoreDraw(node)
})
// 重新装载节点
const layer = new Konva.Layer()
layer.add(...nodes)
nodes = layer.getChildren()
// 计算节点占用的区域
let minX = 0
let maxX = copy.width()
let minY = 0
let maxY = copy.height()
for (const node of nodes) {
const x = node.x()
const y = node.y()
const width = node.width()
const height = node.height()
if (x < minX) {
minX = x
}
if (x + width > maxX) {
maxX = x + width
}
if (y < minY) {
minY = y
}
if (y + height > maxY) {
maxY = y + height
}
if (node.attrs.nodeMousedownPos) {
// 修正正在选中的节点透明度
node.setAttrs({
opacity: copy.attrs.lastOpacity ?? 1
})
}
}
// 重新装载 layer
copy.add(layer)
// 节点占用的区域
copy.setAttrs({
x: -minX,
y: -minY,
scale: { x: 1, y: 1 },
width: maxX - minX,
height: maxY - minY
})
// 返回可视节点和 layer
return copy
}
1、首先复制一份画布
2、取出 main layer
3、筛选目标节点
4、计算目标节点占用区域
5、调整拷贝画布的位置和大小
导出 JSON
使用 stage 的 toJSON 即可。
// 保存
save() {
const copy = this.getView()
// 通过 stage api 导出 json
return copy.toJSON()
}
导入 JSON,恢复画布
相比导出,过程会比较复杂一些。
恢复节点结构
// 恢复
async restore(json: string, silent = false) {
try {
// 清空选择
this.render.selectionTool.selectingClear()
// 清空 main layer 节点
this.render.layer.removeChildren()
// 加载 json,提取节点
const container = document.createElement('div')
const stage = Konva.Node.create(json, container)
const main = stage.getChildren()[0]
const nodes = main.getChildren()
// 恢复节点图片素材
await this.restoreImage(nodes)
// 往 main layer 插入新节点
this.render.layer.add(...nodes)
// 上一步、下一步 无需更新 history 记录
if (!silent) {
// 更新历史
this.render.updateHistory()
}
} catch (e) {
console.error(e)
}
}
1、清空选择
2、清空 main layer 节点
3、创建临时 stage
4、通过 Konva.Node.create 恢复 JSON 定义的节点结构
5、恢复图片素材(关键)
恢复图片素材
// 加载 image(用于导入)
loadImage(src: string) {
return new Promise<HTMLImageElement | null>((resolve) => {
const img = new Image()
img.onload = () => {
// 返回加载完成的图片 element
resolve(img)
}
img.onerror = () => {
resolve(null)
}
img.src = src
})
}
// 恢复图片(用于导入)
async restoreImage(nodes: Konva.Node[] = []) {
for (const node of nodes) {
if (node instanceof Konva.Group) {
// 递归
await this.restoreImage(node.getChildren())
} else if (node instanceof Konva.Image) {
// 处理图片
if (node.attrs.svgXML) {
// svg 素材
const blob = new Blob([node.attrs.svgXML], { type: 'image/svg+xml' })
// dataurl
const url = URL.createObjectURL(blob)
// 加载为图片 element
const image = await this.loadImage(url)
if (image) {
// 设置图片
node.image(image)
}
} else if (node.attrs.gif) {
// gif 素材
const imageNode = await this.render.assetTool.loadGif(node.attrs.gif)
if (imageNode) {
// 设置图片
node.image(imageNode.image())
}
} else if (node.attrs.src) {
// 其他图片素材
const image = await this.loadImage(node.attrs.src)
if (image) {
// 设置图片
node.image(image)
}
}
}
}
}
关于恢复 svg,关键在于拖入 svg 的时候,记录了完整的 svg xml 在属性 svgXML 中。
关于恢复 gif、其他图片,拖入的时候记录其 src 地址,就可以恢复到节点中。
上一步、下一步
其实就是需要记录历史记录
历史记录
history: string[] = []
historyIndex = -1
updateHistory() {
this.history.splice(this.historyIndex + 1)
this.history.push(this.importExportTool.save())
this.historyIndex = this.history.length - 1
// 历史变化事件
this.config.on?.historyChange?.(_.clone(this.history), this.historyIndex)
}
1、从当前历史位置,舍弃后面的记录
2、从当前历史位置,覆盖最新的 JSON 记录
3、更新位置
4、暴露事件(用于外部判断历史状态,以此禁用、启用上一步、下一步)
更新历史记录
一切会产生变动的位置都执行 updateHistory,如拖入素材、移动节点、改变节点位置、改变节点大小、复制粘贴节点、删除节点、改变节点的层次。具体代码就不贴了,只是在影响的地方执行一句:
this.render.updateHistory()
上一步、下一步方法
prevHistory() {
const record = this.history[this.historyIndex - 1]
if (record) {
this.importExportTool.restore(record, true)
this.historyIndex--
// 历史变化事件
this.config.on?.historyChange?.(_.clone(this.history), this.historyIndex)
}
}
nextHistory() {
const record = this.history[this.historyIndex + 1]
if (record) {
this.importExportTool.restore(record, true)
this.historyIndex++
// 历史变化事件
this.config.on?.historyChange?.(_.clone(this.history), this.historyIndex)
}
}
另存为图片
// 获取图片
getImage(pixelRatio = 1, bgColor?: string) {
// 获取可视节点和 layer
const copy = this.getView()
// 背景层
const bgLayer = new Konva.Layer()
// 背景矩形
const bg = new Konva.Rect({
listening: false
})
bg.setAttrs({
x: -copy.x(),
y: -copy.y(),
width: copy.width(),
height: copy.height(),
fill: bgColor
})
// 添加背景
bgLayer.add(bg)
// 插入背景
const children = copy.getChildren()
copy.removeChildren()
copy.add(bgLayer)
copy.add(children[0], ...children.slice(1))
// 通过 stage api 导出图片
return copy.toDataURL({ pixelRatio })
}
主要关注有2点:
1、插入背景层
2、设置导出图片的尺寸
导出的时候,其实就是把当前矢量、非矢量素材统一输出为非矢量的图片,设置导出图片的尺寸越大,可以保留更多的矢量素材细节。
接下来,计划实现下面这些功能:
- 实时预览窗
- 对齐效果
- 连接线
- 等等。。。
是不是值得更多的 Star 呢?勾勾手指~
前端使用 Konva 实现可视化设计器(7)- 导入导出、上一步、下一步的更多相关文章
- 惊闻企业Web应用生成平台 活字格 V4.0 免费了,不单可视化设计器免费,服务器也免费!
官网消息: 针对活字格开发者,新版本完全免费!您可下载活字格 Web 应用生成平台 V4.0 Updated 1,方便的创建各类 Web 应用系统,任意部署,永不过期. 我之前学习过活字格,也曾经向用 ...
- ActiveReports 9 新功能:可视化查询设计器(VQD)介绍
在最新发布的ActiveReports 9报表控件中添加了多项新功能,以帮助你在更短的时间里创建外观绚丽.功能强大的报表系统,本文将重点介绍可视化数据查询设计器,无需手动编写任何SQL语句,主要内容如 ...
- (原创)【B4A】一步一步入门02:可视化界面设计器、控件的使用
一.前言 上篇 (原创)[B4A]一步一步入门01:简介.开发环境搭建.HelloWorld 中我们创建了默认的项目,现在我们来看一下B4A项目的构成,以及如何所见即所得的设计界面,并添加和使用自带的 ...
- Windows Phone 十二、设计器同步
在设计阶段为页面添加数据源 Blend或者VS的可视化设计器会跑我们的代码,然后来显示出来,当我们Build之后,设计器会进入页面的构造函数,调用InitializeComponent();方法来将U ...
- WinForms项目升级.Net Core 3.0之后,没有WinForm设计器?
目录 .NET Conf 2019 Window Forms 设计器 .NET Conf 2019 2019 9.23-9.25召开了 .NET Conf 2019 大会,大会宣布了 .Net Cor ...
- C#基础系列:开发自己的窗体设计器(PropertyGrid显示中文属性名)
既然是一个窗体设计器,那就应该能够设置控件的属性,设置属性最好的当然是PropertyGrid了,我们仅仅需要使用一个PropertyGrid.SelectedObject = Control就可以搞 ...
- 通过用 .NET 生成自定义窗体设计器来定制应用程序
通过用 .NET 生成自定义窗体设计器来定制应用程序 https://www.microsoft.com/china/MSDN/library/netFramework/netframework/Cu ...
- Qt编写自定义控件属性设计器
以前做.NET开发中,.NET直接就集成了属性设计器,VS不愧是宇宙第一IDE,你能够想到的都给你封装好了,用起来不要太爽!因为项目需要自从全面转Qt开发已经6年有余,在工业控制领域,有一些应用场景需 ...
- VS2015 android 设计器不能可视化问题解决。
近期安装了VS2015,体验了一下android 的开发,按模板创建执行了个,试下效果非常不错.也能够可视化设计.但昨天再次打开或创建一个android程序后,设计界面直接不能显示,显示错误:(可能是 ...
- 可视化流程设计——流程设计器演示(基于Silverlight)
上一篇文章<通用流程设计>对鄙人写的通用流程做了一定的介绍,并奉上了相关源码.但一个好的流程设计必少不了流程设计器的支持,本文将针对<通用流程设计>中的流程的设计器做一个简单的 ...
随机推荐
- Lifecycle详细分析
Lifecycle源码分析 目录介绍 01.Lifecycle的作用是什么 02.Lifecycle的简单使用 03.Lifecycle的使用场景 04.如何实现生命周期感知 05.注解方法如何被调用 ...
- 自定义MarkupExtension的学习,以及WPF中Combobox绑定枚举类型
我们上一期讲到ComBobox绑定数据,https://www.cnblogs.com/guchen33/p/17630808.html 这次我们简单化一下, 正常来讲WPF中绑定不了枚举的像这样 / ...
- 一周内容分享(第 10 期):别让自己"墙"了自己
这里记录过去一周,我看到的值得分享的东西. 一方面是整理记录一下自己一周的学习,另一方面也是期待自己有更多的输出,有更多的价值. 周刊开源(Github:wmyskxz/weekly),欢迎提交 is ...
- Oracle PL/SQL 中的 CHAR 和 VARCHAR2 比较
https://docs.oracle.com/cd/B14117_01/appdev.101/b10807/b_char.htm SQL 标准要求被比较的两个字符值具有相等的长度.如果比较中的两个值 ...
- AXI-自定义IP-PS设计
基于AXI4的自定义IP核的设计 1.实验目标 利用vivado中内置的AXI总线初始化的IP核设计工具,将自己设计的流水灯的PL代码打包成IP核外设,挂在PS上,通过PS上的按钮控制流水灯的开始和结 ...
- KingbaseESV8R6识别IO使用率过高
前言 数据库正常运行离不开I/O的使用,在操作系统上,I/O又离不开存储的性能及使用方式,我们可以在存储层利用raid条带化技术使IOPS达到最佳性能. 本篇文章有助于确认数据库I/O使用率过高的原因 ...
- 敏捷MVP面面观
在过去的十年中,软件开发经历了许多阶段.从使流程敏捷高效到使用DevOps简化IT服务,已经有了许多突破,MVP是对软件开发过程产生了根本性影响的进步之一.本文将深入探讨MVP在软件开发中怎样起作用. ...
- Ryoku 的新年欢乐赛
目录 前言 洛谷 6033 Ryoku 的探索 题目 分析 代码 洛谷 6034 Ryoku 与最初之人笔记 题目 分析O(log^2n) 代码(赛时AC) 分析O(logn) 代码(赛后) 洛谷 6 ...
- #线段树、树状数组#D 筹备计划
分析 首先这个位置应该是带权中位数\((\geq \frac{sum+1}{2}(奇数要加一,WA了几次了))\),但是既然有这个选择的限制, 那么要用线段树求出可选择的前驱和后继,然后用树状数组计算 ...
- 使用OHOS SDK构建filament
参照OHOS IDE和SDK的安装方法配置好开发环境. 从gitee下载源码. 执行如下命令: git clone https://gitee.com/oh-graphics/filament.git ...