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

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

而 transformer 的 dragBoundFunc 中的逻辑,也直接移动 transformer 的 dragmove 事件中处理。
请大家动动小手,给我一个免费的 Star 吧~

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

github源码

gitee源码

示例地址

磁贴效果

放大缩小点磁贴网格效果


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

ts 复制代码
    // 变换中
    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 获得拖动期间的坐标
  • 计算离四周网格的距离和偏移量
  • 横向、纵向分别找到达到接近阈值,且距离最近的那个网格坐标(偏移量最小)
  • 把选中的所有节点进行坐标修正

核心逻辑:

ts 复制代码
  // 磁吸逻辑
  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源码

示例地址

相关推荐
崔庆才丨静觅6 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60617 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了7 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅7 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅7 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅8 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment8 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅8 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊8 小时前
jwt介绍
前端
爱敲代码的小鱼8 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax