前端使用 Konva 实现可视化设计器(5)

前端,使用,konva,实现,可视化,设计 · 浏览次数 : 33

小编点评

## 评论 这篇文章介绍了 transformer 的 dragBoundFunc 和 dragmove 事件的逻辑,并简化了 selectingNodesArea 函数。 **文章亮点:** * 文章清晰易懂,并以易懂的代码展示了 transformer 的 dragBoundFunc 和 dragmove 事件的逻辑。 * 文章简化了 selectingNodesArea 函数的逻辑,使其更加简洁易用。 * 文章提供了代码示例,可以帮助理解 transformer 的 dragBoundFunc 和 dragmove 事件的逻辑。 **建议:** * 可以考虑在文章中添加一些关于 transformer 的背景知识,例如它如何用于图像处理等。 * 可以补充一些示例代码,可以帮助读者更直观地理解 transformer 的 dragBoundFunc 和 dragmove 事件的逻辑。 * 可以讨论一下如何实现其他功能,例如实时预览窗导出、导入节点层次单个、批量调整键盘复制、粘贴对齐效果等。 **代码示例:** 文章没有提供代码示例,因此我无法从文中提取任何代码示例。 **总结:** 这篇文章介绍了 transformer 的 dragBoundFunc 和 dragmove 事件的逻辑,并简化了 selectingNodesArea 函数的逻辑。文章提供了代码示例,可以帮助读者理解 transformer 的 dragBoundFunc 和 dragmove 事件的逻辑。

正文

关于第三章提到的 selectingNodesArea,在后续的实现中已经精简掉了。

而 transformer 的 dragBoundFunc 中的逻辑,也直接移动 transformer 的 dragmove 事件中处理。

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

这一章花了比较多的时间调试,创作不易~

github源码

gitee源码

示例地址

磁贴效果

放大缩小点磁贴网格效果

在这里插入图片描述
在这里插入图片描述

官方提供的便捷的 api 可以实现该效果,就是 transformer 的 anchorDragBoundFunc,官方实例,在此基础上,根据当前设计进行实现。

    // 变换中
    anchorDragBoundFunc: (oldPos: Konva.Vector2d, newPos: Konva.Vector2d) => {
      // 磁贴逻辑

      if (this.render.config.attractResize) {
        // transformer 锚点按钮
        const anchor = this.render.transformer.getActiveAnchor()

        // 非旋转(就是放大缩小时)
        if (anchor && anchor !== 'rotater') {
          // stage 状态
          const stageState = this.render.getStageState()

          const logicX = this.render.toStageValue(newPos.x - stageState.x) // x坐标
          const logicNumX = Math.round(logicX / this.render.bgSize) // x单元格个数
          const logicClosestX = logicNumX * this.render.bgSize // x磁贴目标坐标
          const logicDiffX = Math.abs(logicX - logicClosestX) // x磁贴偏移量
          const snappedX = /-(left|right)$/.test(anchor) && logicDiffX < 5 // x磁贴阈值

          const logicY = this.render.toStageValue(newPos.y - stageState.y) // y坐标
          const logicNumY = Math.round(logicY / this.render.bgSize) // y单元格个数
          const logicClosestY = logicNumY * this.render.bgSize // y磁贴目标坐标
          const logicDiffY = Math.abs(logicY - logicClosestY) // y磁贴偏移量
          const snappedY = /^(top|bottom)-/.test(anchor) && logicDiffY < 5 // y磁贴阈值

          if (snappedX && !snappedY) {
            // x磁贴
            return {
              x: this.render.toBoardValue(logicClosestX) + stageState.x,
              y: oldPos.y
            }
          } else if (snappedY && !snappedX) {
            // y磁贴
            return {
              x: oldPos.x,
              y: this.render.toBoardValue(logicClosestY) + stageState.y
            }
          } else if (snappedX && snappedY) {
            // xy磁贴
            return {
              x: this.render.toBoardValue(logicClosestX) + stageState.x,
              y: this.render.toBoardValue(logicClosestY) + stageState.y
            }
          }
        }
      }

      // 不磁贴
      return newPos
    }

主要的逻辑:根据最新的坐标,找到最接近的网格,达到设计的阈值就按官方 api 的定义,返回修正过的坐标(视觉上),所以返回之前,把计算好的“逻辑坐标”用 toBoardValue 恢复成“视觉坐标”。

移动磁贴网格效果

在这里插入图片描述
在这里插入图片描述

这个功能实现起来比较麻烦,官方是没有像类似 anchorDragBoundFunc 这样的 api,需要在 transformer 的 dragmove 介入修改。官方有个对齐线示例,也是“磁贴”相关的,证明在 transformer 的 dragmove 入手是合理的。主要差异是,示例是针对单个节点控制的,本设计是要控制在 transformer 中的多个节点的。

主要流程:

  • 通过 transformer 的 dragmove 获得拖动期间的坐标
  • 计算离四周网格的距离和偏移量
  • 横向、纵向分别找到达到接近阈值,且距离最近的那个网格坐标(偏移量最小)
  • 把选中的所有节点进行坐标修正

核心逻辑:

  // 磁吸逻辑
  attract = (newPos: Konva.Vector2d) => {
    // stage 状态
    const stageState = this.render.getStageState()

    const width = this.render.transformer.width()
    const height = this.render.transformer.height()

    let newPosX = newPos.x
    let newPosY = newPos.y

    let isAttract = false

    if (this.render.config.attractBg) {
      const logicLeftX = this.render.toStageValue(newPos.x - stageState.x) // x坐标
      const logicNumLeftX = Math.round(logicLeftX / this.render.bgSize) // x单元格个数
      const logicClosestLeftX = logicNumLeftX * this.render.bgSize // x磁贴目标坐标
      const logicDiffLeftX = Math.abs(logicLeftX - logicClosestLeftX) // x磁贴偏移量

      const logicRightX = this.render.toStageValue(newPos.x + width - stageState.x) // x坐标
      const logicNumRightX = Math.round(logicRightX / this.render.bgSize) // x单元格个数
      const logicClosestRightX = logicNumRightX * this.render.bgSize // x磁贴目标坐标
      const logicDiffRightX = Math.abs(logicRightX - logicClosestRightX) // x磁贴偏移量

      const logicTopY = this.render.toStageValue(newPos.y - stageState.y) // y坐标
      const logicNumTopY = Math.round(logicTopY / this.render.bgSize) // y单元格个数
      const logicClosestTopY = logicNumTopY * this.render.bgSize // y磁贴目标坐标
      const logicDiffTopY = Math.abs(logicTopY - logicClosestTopY) // y磁贴偏移量

      const logicBottomY = this.render.toStageValue(newPos.y + height - stageState.y) // y坐标
      const logicNumBottomY = Math.round(logicBottomY / this.render.bgSize) // y单元格个数
      const logicClosestBottomY = logicNumBottomY * this.render.bgSize // y磁贴目标坐标
      const logicDiffBottomY = Math.abs(logicBottomY - logicClosestBottomY) // y磁贴偏移量

      // 距离近优先

      for (const diff of [
        { type: 'leftX', value: logicDiffLeftX },
        { type: 'rightX', value: logicDiffRightX }
      ].sort((a, b) => a.value - b.value)) {
        if (diff.value < 5) {
          if (diff.type === 'leftX') {
            newPosX = this.render.toBoardValue(logicClosestLeftX) + stageState.x
          } else if (diff.type === 'rightX') {
            newPosX = this.render.toBoardValue(logicClosestRightX) + stageState.x - width
          }
          isAttract = true
          break
        }
      }

      for (const diff of [
        { type: 'topY', value: logicDiffTopY },
        { type: 'bottomY', value: logicDiffBottomY }
      ].sort((a, b) => a.value - b.value)) {
        if (diff.value < 5) {
          if (diff.type === 'topY') {
            newPosY = this.render.toBoardValue(logicClosestTopY) + stageState.y
          } else if (diff.type === 'bottomY') {
            newPosY = this.render.toBoardValue(logicClosestBottomY) + stageState.y - height
          }
          isAttract = true
          break
        }
      }
    }

    return {
      pos: {
        x: newPosX,
        y: newPosY
      },
      isAttract
    }
  }

这段逻辑及其相关事件的改动,不下 5 次,才勉强达到预期的效果。

接下来,计划实现下面这些功能:

  • 实时预览窗
  • 导出、导入
  • 节点层次单个、批量调整
  • 键盘复制、粘贴
  • 对齐效果
  • 等等。。。

是不是值得更多的 Star 呢?勾勾手指~

源码

gitee源码

示例地址

与前端使用 Konva 实现可视化设计器(5)相似的内容:

前端使用 Konva 实现可视化设计器(5)

关于第三章提到的 selectingNodesArea,在后续的实现中已经精简掉了。 而 transformer 的 dragBoundFunc 中的逻辑,也直接移动 transformer 的 dragmove 事件中处理。 请大家动动小手,给我一个免费的 Star 吧~ 这一章花了比较多的时间调

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

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

前端使用 Konva 实现可视化设计器(16)- 旋转对齐、触摸板操作的优化

这一章解决两个缺陷,一是调整一些快捷键,使得 Mac 触摸板可以正常操作;二是修复一个 Issue,使得即使素材节点即使被旋转之后,也能正常触发磁贴对齐效果,有个小坑需要注意。

前端使用 Konva 实现可视化设计器(15)- 自定义连接点、连接优化

本章将处理一些缺陷的同时,实现支持连接点的自定义,一个节点可以定义多个连接点,最终可以满足类似图元接线的效果。

前端使用 Konva 实现可视化设计器(14)- 折线 - 最优路径应用【代码篇】

话接上回[《前端使用 Konva 实现可视化设计器(13)- 折线 - 最优路径应用【思路篇】》](https://www.cnblogs.com/xachary/p/18238704),这一章继续说说相关的代码如何构思的,如何一步步构建数据模型可供 AStar 算法进行路径规划,最终画出节点之间的...

前端使用 Konva 实现可视化设计器(13)- 折线 - 最优路径应用【思路篇】

这一章把直线连接改为折线连接,沿用原来连接点的关系信息。关于折线的计算,使用的是开源的 AStar 算法进行路径规划,启发方式为 曼哈顿距离,且不允许对角线移动。 请大家动动小手,给我一个免费的 Star 吧~ 大家如果发现了 Bug,欢迎来提 Issue 哟~ github源码 gitee源码 示

前端使用 Konva 实现可视化设计器(12)- 连接线 - 直线

这一章实现的连接线,目前仅支持直线连接,为了能够不影响原有的其它功能,尝试了2、3个实现思路,最终实测这个实现方式目前来说最为合适了。 请大家动动小手,给我一个免费的 Star 吧~ 大家如果发现了 Bug,欢迎来提 Issue 哟~ github源码 gitee源码 示例地址 相关定义 连接点 记

前端使用 Konva 实现可视化设计器(11)- 对齐效果

这一章补充一个效果,在多选的情况下,对目标进行对齐。基于多选整体区域对齐的基础上,还支持基于其中一个节点进行对齐。

前端使用 Konva 实现可视化设计器(10)- 对齐线

前端使用 Konva 实现可视化设计器,这次实现对齐线的交互功能,单个、多个、多选都可以对齐,同时还能磁贴。

前端使用 Konva 实现可视化设计器(9)- 另存为SVG

请大家动动小手,给我一个免费的 Star 吧~ 大家如果发现了 Bug,欢迎来提 Issue 哟~ github源码 gitee源码 示例地址 另存为SVG 这一章增强了另存为的能力,实现“另存为SVG”,大概是全网唯一的实例分享了吧。 灵感来源:react-konva-custom-context