首页 > 其他分享 >前端使用 Konva 实现可视化设计器(18)- 素材嵌套 - 加载阶段

前端使用 Konva 实现可视化设计器(18)- 素材嵌套 - 加载阶段

时间:2024-07-22 18:28:45浏览次数:14  
标签:node const 18 Konva 嵌套 json asset pair

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

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

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

github源码

gitee源码

示例地址

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

// src/Render/handlers/DragOutsideHandlers.ts

drop: (e: GlobalEventHandlersEventMap['drop']) => {
  // 略
  this.render.assetTool[
    type === 'svg'
    ? `loadSvg`
    : type === 'gif'
    ? 'loadGif'
    : type === 'json'
    ? 'loadJson' // 新增,处理 json 类型素材
    : 'loadImg'
  ](src).then((target: Konva.Image | Konva.Group) => {
    // 图片素材
    if (target instanceof Konva.Image) {
      // 略
    } else {
      // json 素材
      target.id(nanoid())
      target.name('asset')
      group = target
      this.render.linkTool.groupIdCover(group)
    }
  })
  // 略
}

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

// src/Render/tools/AssetTool.ts

  // 加载节点 json
  async loadJson(src: string) {
    try {
      // 读取 json内容
      const json = JSON.parse(await (await fetch(src)).text())

      // 子素材
      const assets = json.children

      // 刷新id
      this.render.linkTool.jsonIdCover(assets)

      // 生成空白 stage+layer
      const stageEmpty = new Konva.Stage({
        container: document.createElement('div')
      })
      const layerEmpty = new Konva.Layer()
      stageEmpty.add(layerEmpty)

      // 空白 json 根
      const jsonRoot = JSON.parse(stageEmpty.toJSON())
      jsonRoot.children[0].children = [json]

      // 重新加载 stage
      const stageReload = Konva.Node.create(JSON.stringify(jsonRoot), document.createElement('div'))

      // 目标 group(即 json 转化后的节点)
      const groupTarget = stageReload.children[0].children[0] as Konva.Group

      // 释放内存
      stageEmpty.destroy()
      groupTarget.remove()
      stageReload.destroy()

      // 深度遍历加载子素材
      const nodes: {
        target: Konva.Stage | Konva.Layer | Konva.Group | Konva.Node
        parent?: Konva.Stage | Konva.Layer | Konva.Group | Konva.Node
      }[] = [{ target: groupTarget }]

      while (nodes.length > 0) {
        const item = nodes.shift()
        if (item) {
          const node = item.target
          if (node instanceof Konva.Image) {
            if (node.attrs.svgXML) {
              const n = await this.loadSvgXML(node.attrs.svgXML)
              n.listening(false)
              node.parent?.add(n)
              node.remove()
            } else if (node.attrs.gif) {
              const n = await this.loadGif(node.attrs.gif)
              n.listening(false)
              node.parent?.add(n)
              node.remove()
            } else if (node.attrs.src) {
              const n = await this.loadImg(node.attrs.src)
              n.listening(false)
              node.parent?.add(n)
              node.remove()
            }
          }
          if (
            node instanceof Konva.Stage ||
            node instanceof Konva.Layer ||
            node instanceof Konva.Group
          ) {
            nodes.push(
              ...node.getChildren().map((o) => ({
                target: o,
                parent: node
              }))
            )
          }
        }
      }

      // 作用:点击空白区域可选择
      const clickMask = new Konva.Rect({
        id: 'click-mask',
        width: groupTarget.width(),
        height: groupTarget.height()
      })
      groupTarget.add(clickMask)
      clickMask.zIndex(1)

      return groupTarget
    } catch (e) {
      console.error(e)
      return new Konva.Group()
    }
  }

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 处理:

// src/Render/tools/LinkTool.ts

  // 把深层 group 的 id 统一为顶层 group 的 id
  groupIdCover(group: Konva.Group) {
    const groupId = group.id()
    const subGroups = group.find('.sub-asset') as Konva.Group[]
    while (subGroups.length > 0) {
      const subGroup = subGroups.shift() as Konva.Group | undefined
      if (subGroup) {
        const points = subGroup.attrs.points
        if (Array.isArray(points)) {
          for (const point of points) {
            point.rawGroupId = point.groupId
            point.groupId = groupId
            for (const pair of point.pairs) {
              pair.from.rawGroupId = pair.from.groupId
              pair.from.groupId = groupId
              pair.to.rawGroupId = pair.to.groupId
              pair.to.groupId = groupId
            }
          }
        }

        subGroups.push(...(subGroup.find('.sub-asset') as Konva.Group[]))
      }
    }
  }

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

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

// src/Render/tools/LinkTool.ts

  pointsVisible(visible: boolean, group?: Konva.Group) {
    const start = group ?? this.render.layer

    // 查找深层 points
    for (const asset of [
      ...(['asset', 'sub-asset'].includes(start.name()) ? [start] : []),
      ...start.find('.asset'),
      ...start.find('.sub-asset')
    ]) {
      const points = asset.getAttr('points') ?? []
      asset.setAttrs({
        points: points.map((o: any) => ({ ...o, visible }))
      })
    }

    // 重绘
    this.render.redraw()
  }

然后,关键要调整 LinkDraw:

// src/Render/draws/LinkDraw.ts

override draw() {
    // 略
  
    // 所有层级的素材
    const groups = [
      ...(this.render.layer.find('.asset') as Konva.Group[]),
      ...(this.render.layer.find('.sub-asset') as Konva.Group[])
    ]
    
    // 略
    
    const pairs = points.reduce((ps, point) => {
      return ps.concat(point.pairs ? point.pairs.filter((o) => !o.disabled) : [])
    }, [] as LinkDrawPair[])
    
    // 略
    
    // 连接线
    for (const pair of pairs) {
      // 多层素材,需要排除内部 pair 对
      // pair 也不能为 disabled
      if (pair.from.groupId !== pair.to.groupId && !pair.disabled) {
        // 略
      }
    }
}

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

2、过滤掉 disabled 的 pair 纪录

3、过滤掉同 asset 的 pair 纪录

其他逻辑,基本不变。

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

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

More Stars please!勾勾手指~

源码

gitee源码

示例地址

标签:node,const,18,Konva,嵌套,json,asset,pair
From: https://www.cnblogs.com/xachary/p/18316657

相关文章

  • 28449-2018 测评过程指南小结
    28449测评过程指南适用于测评机构、定级对象的主管部门及运营使用单位对定级对象安全等级保护状况开展安全测试评价等级测评工作要求:(附录C)1依据标准,遵循原则2恰当选取,保证强度3规范行为,规避风险存在的风险:(标准P1和课本108)1.影响系统正常运行2.泄露敏感信息3.......
  • [CEOI2018] Lottery 题解
    前言题目链接:洛谷。题意简述给出序列\(a_1\ldotsa_n\)和常数\(l\leqn\),定义:\[\operatorname{dis}(i,j)=\sum_{k=0}^{l-1}[a_{i+k}\neqa_{j+k}]\qquad(i,j\in[1,n-l+1])\]每次询问一个\(k\),求对于所有\(i\in[1,n-l+1]\),求\(\sum......
  • 【日记】坏了,0721 真成为柚子厨的标记了(418 字)
    正文今天是7月21号,0721,然后柚子社入驻B站了,开始我以为是整活,结果发现是真的。草,这下0721真成柚子厨纪念日了。有点难绷又有点好笑。睡觉的一天。我原以为14:30睡到16:30差不多了,結果一觉睡到17:30。草。我想着周末,也就没设闹钟了,睡到什么时候随缘。渐渐习......
  • 西门子博图TIA V18软件安装步骤
    目录标题STEP1准备好安装包并解压STEP2打开.NET3.5STEP3安装V18主要软件STEP4安装PLCSIMV18仿真软件STEP5安装startdrive调试变频器软件(选装)STEP6安装完成STEP1准备好安装包并解压首先准备好TIAV18软件安装包解压一下准备安装STEP2打开.NET3.5博图T......
  • 具有未定义嵌套列表深度的嵌套列表的Python注释类型
    [[1,2,3],3,[2,4],5]的类型是list[list[int]|int]但是,如果嵌套列表具有未定义的深度,例如[[[1,2]],2,[1,[3,[3]]]],3,[2,[2]]],那么它会具有什么类型?可以使用递归类型提示来表示任意深度嵌套的列表:fromtypingimportList,Union......
  • 交叉编译ethtool(ubuntu 2018)
    参考文章:https://www.cnblogs.com/nazhen/p/16800427.htmlhttps://blog.csdn.net/weixin_43128044/article/details/1379539131、下载相关安装包//ethtool依赖libmulgitclonehttp://git.netfilter.org/libmnl//ethtool源码gitclonehttp://git.kernel.org/pub/sc......
  • 【嵌入式DIY实例-ESP8266篇】- LCD ST7789显示DS18B20传感器数据
    LCDST7789显示DS18B20传感器数据文章目录LCDST7789显示DS18B20传感器数据1、硬件准备与接线2、代码实现本文介绍如何使用ESP8266NodeMCU开发板(ESP-12E)和DS18B20数字温度传感器实现简单的数字温度计。在此项目中,NodeMCU板从DS18B20传感器读......
  • KubeSphere 社区双周报|07.05-07.18
    KubeSphere社区双周报主要整理展示新增的贡献者名单和证书、新增的讲师证书以及两周内提交过commit的贡献者,并对近期重要的PR进行解析,同时还包含了线上/线下活动和布道推广等一系列社区动态。本次双周报涵盖时间为:2024.07.05-07.18。贡献者名单新晋KubeSpherecontribu......
  • Ubuntu18.04 安装 Cuckoo Sandbox (第二部分 安装沙盒)
    Ubuntu18.04安装CuckooSandbox(第二部分安装沙盒)0x00初步查看虚拟机环境依据cuckoo官网首先,有必要先查看虚拟机此时的python版本,gcc版本我们看到Ubuntu18.04本身支持的是python2.7版本,因此使用该虚拟机会比使用20版更加便利,cuckoo只支持python2.7版本,Ubuntu自带的pyth......
  • 1186. 删除一次得到子数组最大和 Medium
    给你一个整数数组,返回它的某个 非空 子数组(连续元素)在执行一次可选的删除操作后,所能得到的最大元素总和。换句话说,你可以从原数组中选出一个子数组,并可以决定要不要从中删除一个元素(只能删一次哦),(删除后)子数组中至少应当有一个元素,然后该子数组(剩下)的元素总和是所有子数组之中......