• 前端使用 Konva 实现可视化设计器(5)- 磁贴效果


    关于第三章提到的 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源码

    示例地址

  • 相关阅读:
    001.第一个C语言项目
    什么???CSS也能原子化!
    多功能气象传感器解析
    通过xsd校验xml
    sql语句去掉括号及中括号中的内容,大型数据库与access数据库
    部署 Wi-Fi 6 之前需要回答的 13 个问题
    ffmpeg解复用指定pid转推udp
    业主方怎么管理固定资产
    基于java+SpringBoot+HTML+Mysql政务审批网站的设计与实现
    docker学习记录(二)
  • 原文地址:https://www.cnblogs.com/xachary/p/18139413