前端使用 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源碼

示例地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章