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


    使用 konva 实现一个设计器交互,首先考虑实现设计器的画布。

    一个基本的画布:

    【展示】网格、比例尺

    【交互】拖拽、缩放

    “拖拽”是无尽的,“缩放”是基于鼠标焦点的。

    最终效果(示例地址):

    image

    image

    image

    基本思路:

    设计区域 HTML 由两个节点构成,内层挂载一个 Konva.stage 作为画布的开始。

    <template>
      <div class="page">
        <header>header>
        <section>
          <header>header>
          <section ref="boardElement">
            <div ref="stageElement">div>
          section>
          <footer>footer>
        section>
        <footer>footer>
      div>
    template>
    

    image

    Konva.stage 暂时先设计3个 Konva.Layer,分别用于绘制背景、所有素材、比例尺。

    image

    通过 ResizeObserver 使 Konva.stage 的大小与外层 boardElement 保持一致。

    为了显示“比例尺” Konva.stage 默认会偏移一些距离,这里定义“比例尺”尺寸为 40px。

        this.stage = new Konva.Stage({
          container: stageEle,
          x: this.rulerSize,
          y: this.rulerSize,
          width: config.width,
          height: config.height
        })
    

    关于“网格背景”,是按照当前设计区域大小、缩放大小、偏移量,计算横向、纵向分别需要绘制多少条 Konva.Line(横向、纵向分别多加1条),同时根据 Konva.stage 的 x,y 进行偏移,用有限的 Konva.Line 模拟无限的网格画布。

          // 格子大小
          const cellSize = this.option.size
          //
          const width = this.stage.width()
          const height = this.stage.height()
          const scaleX = this.stage.scaleX()
          const scaleY = this.stage.scaleY()
          const stageX = this.stage.x()
          const stageY = this.stage.y()
    
          // 列数
          const lenX = Math.ceil(width / scaleX / cellSize)
          // 行数
          const lenY = Math.ceil(height / scaleY / cellSize)
    
          const startX = -Math.ceil(stageX / scaleX / cellSize)
          const startY = -Math.ceil(stageY / scaleY / cellSize)
    
          const group = new Konva.Group()
    
          group.add(
            new Konva.Rect({
              name: this.constructor.name,
              x: 0,
              y: 0,
              width: width,
              height: height,
              stroke: 'rgba(255,0,0,0.1)',
              strokeWidth: 2 / scaleY,
              listening: false,
              dash: [4, 4]
            })
          )
    
          // 竖线
          for (let x = startX; x < lenX + startX + 1; x++) {
            group.add(
              new Konva.Line({
                name: this.constructor.name,
                points: _.flatten([
                  [cellSize * x, -stageY / scaleY],
                  [cellSize * x, (height - stageY) / scaleY]
                ]),
                stroke: '#ddd',
                strokeWidth: 1 / scaleY,
                listening: false
              })
            )
          }
    
          // 横线
          for (let y = startY; y < lenY + startY + 1; y++) {
            group.add(
              new Konva.Line({
                name: this.constructor.name,
                points: _.flatten([
                  [-stageX / scaleX, cellSize * y],
                  [(width - stageX) / scaleX, cellSize * y]
                ]),
                stroke: '#ddd',
                strokeWidth: 1 / scaleX,
                listening: false
              })
            )
          }
    
          this.group.add(group)
    

    关于“比例尺”,与“网格背景”思路差不多,在绘制“刻度”和“数值”的时候相对麻烦一些,例如绘制“数值”的时候,需要动态判断应该使用多大的字体。

                  let fontSize = fontSizeMax
    
                  const text = new Konva.Text({
                    name: this.constructor.name,
                    y: this.option.size / scaleY / 2 - fontSize / scaleY,
                    text: (x * cellSize).toString(),
                    fontSize: fontSize / scaleY,
                    fill: '#999',
                    align: 'center',
                    verticalAlign: 'bottom',
                    lineHeight: 1.6
                  })
    
                  while (text.width() / scaleY > (cellSize / scaleY) * 4.6) {
                    fontSize -= 1
                    text.fontSize(fontSize / scaleY)
                    text.y(this.option.size / scaleY / 2 - fontSize / scaleY)
                  }
                  text.x(nx - text.width() / 2)
    

    关于“拖拽”,这里设计的是通过鼠标右键拖拽画布,通过记录 mousedown 时 Konva.stage 起始位置、鼠标位置,mousemove 时将鼠标位置偏移与Konva.stage 起始位置计算最新的 Konva.stage 的位置即可。

          mousedown: (e: Konva.KonvaEventObject'mousedown']>) => {
            if (e.evt.button === Types.MouseButton.右键) {
              // 鼠标右键
              this.mousedownRight = true
    
              this.mousedownPosition = { x: this.render.stage.x(), y: this.render.stage.y() }
              const pos = this.render.stage.getPointerPosition()
              if (pos) {
                this.mousedownPointerPosition = { x: pos.x, y: pos.y }
              }
    
              document.body.style.cursor = 'pointer'
            }
          },
          mouseup: () => {
            this.mousedownRight = false
    
            document.body.style.cursor = 'default'
          },
          mousemove: () => {
            if (this.mousedownRight) {
              // 鼠标右键拖动
              const pos = this.render.stage.getPointerPosition()
              if (pos) {
                const offsetX = pos.x - this.mousedownPointerPosition.x
                const offsetY = pos.y - this.mousedownPointerPosition.y
                this.render.stage.position({
                  x: this.mousedownPosition.x + offsetX,
                  y: this.mousedownPosition.y + offsetY
                })
    
                // 更新背景
                this.render.draws[Draws.BgDraw.name].draw()
                // 更新比例尺
                this.render.draws[Draws.RulerDraw.name].draw()
              }
            }
          }
    

    关于“缩放”,可以参考 konva 官网的缩放示例,思路是差不多的,只是根据实际情况调整了逻辑。

    接下来,计划增加下面功能:

    • 坐标参考线
    • 从左侧图片素材拖入节点
    • 鼠标、键盘移动节点
    • 鼠标、键盘单选、多选节点
    • 键盘复制、粘贴
    • 节点层次单个、批量调整
    • 等等。。。

    如果 github Star 能超过 20 个,将很快更新下一篇章。

    源码在这,望多多支持

  • 相关阅读:
    重建二叉树(前序+中序配合)
    Docker、Jenkins、Git 自动化部署 SpringBoot 项目(从零到搭建完成)
    Git新技能-stash操作
    (undone) 如何计算 Hessian Matrix 海森矩阵 海塞矩阵
    Ubuntu 命令行设置静态IP地址方法
    LeetCode 刷题 [C++] 第236题.二叉树的最近公共祖先
    20240329-1-SVM面试题
    经典进程同步问题
    国产高精度比较器MS762/M、MS761 对标SGM8742YMS8
    PAT乙级真题练习:1001-1004
  • 原文地址:https://www.cnblogs.com/xachary/p/18115479