经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » 编程经验 » 查看文章
前端使用 Konva 实现可视化设计器(18)- 素材嵌套 - 加载阶段
来源:cnblogs  作者:xachary  时间:2024/7/23 9:08:12  对本文有异议

本章主要实现素材的嵌套(加载阶段)这意味着可以拖入画布的对象,不只是图片素材,还可以是嵌套的图片和图形。

请大家动动小手,给我一个免费的 Star 吧~

大家如果发现了 Bug,欢迎来提 Issue 哟~

github源码

gitee源码

示例地址

在原来的 drop 处理基础上,增加一个 json 类型素材的处理入口:

  1. // src/Render/handlers/DragOutsideHandlers.ts
  2. drop: (e: GlobalEventHandlersEventMap['drop']) => {
  3. // 略
  4. this.render.assetTool[
  5. type === 'svg'
  6. ? `loadSvg`
  7. : type === 'gif'
  8. ? 'loadGif'
  9. : type === 'json'
  10. ? 'loadJson' // 新增,处理 json 类型素材
  11. : 'loadImg'
  12. ](src).then((target: Konva.Image | Konva.Group) => {
  13. // 图片素材
  14. if (target instanceof Konva.Image) {
  15. // 略
  16. } else {
  17. // json 素材
  18. target.id(nanoid())
  19. target.name('asset')
  20. group = target
  21. this.render.linkTool.groupIdCover(group)
  22. }
  23. })
  24. // 略
  25. }

drop 原逻辑基本不变,关键逻辑在 loadJson 中:

  1. // src/Render/tools/AssetTool.ts
  2. // 加载节点 json
  3. async loadJson(src: string) {
  4. try {
  5. // 读取 json内容
  6. const json = JSON.parse(await (await fetch(src)).text())
  7. // 子素材
  8. const assets = json.children
  9. // 刷新id
  10. this.render.linkTool.jsonIdCover(assets)
  11. // 生成空白 stage+layer
  12. const stageEmpty = new Konva.Stage({
  13. container: document.createElement('div')
  14. })
  15. const layerEmpty = new Konva.Layer()
  16. stageEmpty.add(layerEmpty)
  17. // 空白 json 根
  18. const jsonRoot = JSON.parse(stageEmpty.toJSON())
  19. jsonRoot.children[0].children = [json]
  20. // 重新加载 stage
  21. const stageReload = Konva.Node.create(JSON.stringify(jsonRoot), document.createElement('div'))
  22. // 目标 group(即 json 转化后的节点)
  23. const groupTarget = stageReload.children[0].children[0] as Konva.Group
  24. // 释放内存
  25. stageEmpty.destroy()
  26. groupTarget.remove()
  27. stageReload.destroy()
  28. // 深度遍历加载子素材
  29. const nodes: {
  30. target: Konva.Stage | Konva.Layer | Konva.Group | Konva.Node
  31. parent?: Konva.Stage | Konva.Layer | Konva.Group | Konva.Node
  32. }[] = [{ target: groupTarget }]
  33. while (nodes.length > 0) {
  34. const item = nodes.shift()
  35. if (item) {
  36. const node = item.target
  37. if (node instanceof Konva.Image) {
  38. if (node.attrs.svgXML) {
  39. const n = await this.loadSvgXML(node.attrs.svgXML)
  40. n.listening(false)
  41. node.parent?.add(n)
  42. node.remove()
  43. } else if (node.attrs.gif) {
  44. const n = await this.loadGif(node.attrs.gif)
  45. n.listening(false)
  46. node.parent?.add(n)
  47. node.remove()
  48. } else if (node.attrs.src) {
  49. const n = await this.loadImg(node.attrs.src)
  50. n.listening(false)
  51. node.parent?.add(n)
  52. node.remove()
  53. }
  54. }
  55. if (
  56. node instanceof Konva.Stage ||
  57. node instanceof Konva.Layer ||
  58. node instanceof Konva.Group
  59. ) {
  60. nodes.push(
  61. ...node.getChildren().map((o) => ({
  62. target: o,
  63. parent: node
  64. }))
  65. )
  66. }
  67. }
  68. }
  69. // 作用:点击空白区域可选择
  70. const clickMask = new Konva.Rect({
  71. id: 'click-mask',
  72. width: groupTarget.width(),
  73. height: groupTarget.height()
  74. })
  75. groupTarget.add(clickMask)
  76. clickMask.zIndex(1)
  77. return groupTarget
  78. } catch (e) {
  79. console.error(e)
  80. return new Konva.Group()
  81. }
  82. }

loadJson,关键逻辑说明:

1、jsonIdCover 把加载到的 json 内部的 id 们刷新一遍

2、借一个空 stage 得到一个 空 stage 的 json 结构(由于素材 json 只包含素材自身结构,需要补充上层 json 结构)

3、加载拼接好的 json,得到一个新 stage

4、从 3 的 stage 中提取目标素材 group

5、加载该 group 内部的图片素材

6、插入一个透明 Rect,使其点击 sub-asset 们之间的空白,也能选中整个 asset

最后,进行一次 linkTool.groupIdCover 处理:

  1. // src/Render/tools/LinkTool.ts
  2. // 把深层 group 的 id 统一为顶层 group 的 id
  3. groupIdCover(group: Konva.Group) {
  4. const groupId = group.id()
  5. const subGroups = group.find('.sub-asset') as Konva.Group[]
  6. while (subGroups.length > 0) {
  7. const subGroup = subGroups.shift() as Konva.Group | undefined
  8. if (subGroup) {
  9. const points = subGroup.attrs.points
  10. if (Array.isArray(points)) {
  11. for (const point of points) {
  12. point.rawGroupId = point.groupId
  13. point.groupId = groupId
  14. for (const pair of point.pairs) {
  15. pair.from.rawGroupId = pair.from.groupId
  16. pair.from.groupId = groupId
  17. pair.to.rawGroupId = pair.to.groupId
  18. pair.to.groupId = groupId
  19. }
  20. }
  21. }
  22. subGroups.push(...(subGroup.find('.sub-asset') as Konva.Group[]))
  23. }
  24. }
  25. }

这里的逻辑就是把 顶层 asset 的新id,通过广度优先遍历,下发到下面所有的 point 和 pair 上,并保留原来的 groupId(上面的 rawGroupId)为日后备用。groupId 更新之后,在连接线算法执行的时候,会忽略同个 asset 下不同 sub-asset 的 pair 关系,即不会重复绘制内部不同 sub-asset 之间实时连接线(连接线在另存为素材 json 的时候,已经直接固化成 Line 实例了,往后将跟随 根 asset 行动,特别是 transform 变换)。

接着,因为这次的实现,内部属于各 sub-asset 的 point 依旧有效,首先,调整一下 pointsVisible,使其在 hover 根 asset 的时候,内部所有 point 都会显现:

  1. // src/Render/tools/LinkTool.ts
  2. pointsVisible(visible: boolean, group?: Konva.Group) {
  3. const start = group ?? this.render.layer
  4. // 查找深层 points
  5. for (const asset of [
  6. ...(['asset', 'sub-asset'].includes(start.name()) ? [start] : []),
  7. ...start.find('.asset'),
  8. ...start.find('.sub-asset')
  9. ]) {
  10. const points = asset.getAttr('points') ?? []
  11. asset.setAttrs({
  12. points: points.map((o: any) => ({ ...o, visible }))
  13. })
  14. }
  15. // 重绘
  16. this.render.redraw()
  17. }

然后,关键要调整 LinkDraw:

  1. // src/Render/draws/LinkDraw.ts
  2. override draw() {
  3. // 略
  4. // 所有层级的素材
  5. const groups = [
  6. ...(this.render.layer.find('.asset') as Konva.Group[]),
  7. ...(this.render.layer.find('.sub-asset') as Konva.Group[])
  8. ]
  9. // 略
  10. const pairs = points.reduce((ps, point) => {
  11. return ps.concat(point.pairs ? point.pairs.filter((o) => !o.disabled) : [])
  12. }, [] as LinkDrawPair[])
  13. // 略
  14. // 连接线
  15. for (const pair of pairs) {
  16. // 多层素材,需要排除内部 pair 对
  17. // pair 也不能为 disabled
  18. if (pair.from.groupId !== pair.to.groupId && !pair.disabled) {
  19. // 略
  20. }
  21. }
  22. }

1、groups 查询要增加包含 sub-asset

2、过滤掉 disabled 的 pair 纪录

3、过滤掉同 asset 的 pair 纪录

其他逻辑,基本不变。

至此,关于“素材嵌套”的逻辑基本已实现。

整体代码对比上个功能版本,改变的并不多,对之前的代码影响不大。

More Stars please!勾勾手指~

源码

gitee源码

示例地址

原文链接:https://www.cnblogs.com/xachary/p/18316657

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号