You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Konva.js中多边形绘制、网格吸附及尺寸修改实现求助

Konva.js 多边形绘制(网格吸附+可编辑尺寸)实现方案

需求说明

需要在Konva.js中实现:

  • 多边形绘制功能
  • 绘制时支持网格吸附
  • 绘制完成后可拖拽顶点修改多边形尺寸

以下是基于提供的代码改造后的完整实现方案:

完整实现代码

<!DOCTYPE html>
<html>

<head>
    <script src="https://unpkg.com/konva@8.4.3/konva.min.js"></script>
    <meta charset="utf-8" />
    <title>Konva 多边形绘制(网格吸附+可编辑)</title>
    <style>
        body {
            padding: 0;
            margin: 0;
            overflow: hidden;
        }

        #container1 {
            display: inline-block;
            width: 600px;
            height: 300px;
            background-color: silver;
            overflow: hidden;
            position: relative;
            background-image: linear-gradient(rgba(0,0,0,.1) 1px, transparent 1px),
                              linear-gradient(90deg, rgba(0,0,0,.1) 1px, transparent 1px);
            background-size: 20px 20px; /* 网格大小 */
        }

        #controls {
            position: absolute;
            top: 4px;
            left: 4px;
        }
    </style>
</head>

<body>
    <div id='container1'></div>
    <div id="controls">
        <button id="export">导出坐标</button>
        <button id="reset">重置画布</button>
        <textarea id="points" style="display:none; width:200px; height:100px;"></textarea>
    </div>
    <script>
        // 网格大小配置
        const GRID_SIZE = 20;

        // 坐标吸附到网格
        function snapToGrid(x, y) {
            return {
                x: Math.round(x / GRID_SIZE) * GRID_SIZE,
                y: Math.round(y / GRID_SIZE) * GRID_SIZE
            };
        }

        // 初始化舞台
        var s1 = new Konva.Stage({container: 'container1', width: 600, height: 300});
        
        // 多边形图层
        var polygonLayer = new Konva.Layer({draggable: false});
        s1.add(polygonLayer);
        
        // 顶点拖拽图层
        var pointLayer = new Konva.Layer({draggable: false});
        s1.add(pointLayer);
        
        // 点击捕获区域
        var r = new Konva.Rect({x:0, y: 0,  width: 600, height: 300, fill: 'black', opacity: 0.1})
        pointLayer.add(r)
        
        s1.draw()
        
        // 可拖拽顶点类
        var DragPoint = function(path, opts, doPush){
          var path = path;
        
          this.x = opts.x;
          this.y = opts.y;
          this.fixed = opts.fixed;
          this.id = randId();
        
          if (doPush){
            path.pts.push(this);  
          }
        
          // 生成随机ID
          function randId() {
             return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(2, 10);
          }
        
          // 标记为固定顶点(绘制时添加的点)
          this.makeFixed = function(){
            this.fixed = true;
            s1.find('#' + this.id).fill(path.fillColor);      
          }
          
          this.kill = function(){
            s1.find('#' + this.id).remove();        
          }
          
          this.draw = function(){
            var circleId = this.id;
         
            var pt = new Konva.Circle({
              id: circleId,
              x: this.x, 
              y: this.y, 
              radius: 5,
              opacity: path.pointOpacity,
              strokeWidth: 2,
              stroke: path.strokeColor,
              fill: 'transparent',
              draggable: true    
            })
            
            pt.on('dragstart', function(){
                path.drawState = 'dragging';
            })
            
            pt.on('dragmove', function(){
              var rawPos = this.getPosition();
              var pos = snapToGrid(rawPos.x, rawPos.y);
              path.updatePt(this.id(), pos);
              path.draw();
            })
            
            pt.on('dragend', function(){
              path.drawState = 'drawing';
              var rawPos = this.getPosition();
              var pos = snapToGrid(rawPos.x, rawPos.y);
              path.updatePt(this.getId(), pos);
              path.draw();
            })
        
            if (this.fixed){
              this.makeFixed();
            }
            
            path.ptLayer.add(pt);
            path.draw();
          }  
        }
        
        // 多边形绘制管理类
        var PolygonPath = function() {
            this.polygonLayer = null;
            this.ptLayer = null;
            this.drawState = '';
            this.pts = []; // 存储顶点
            this.drawingPolygon = null;
        
            // 添加顶点
            this.addPt = function(pos, isFixed){ 
              if (this.drawState === 'dragging'){
                return null;
              }
              
              // 坐标吸附到网格
              const snappedPos = snapToGrid(pos.x, pos.y);
        
              var pt = new DragPoint(this, {x: snappedPos.x, y: snappedPos.y, fixed: isFixed}, true);
              pt.draw();
              pt.makeFixed();
              this.draw();
            }
        
            // 更新顶点位置
            this.updatePt = function(id, pos){
              for (var i = 0; i < this.pts.length; i++){
                if (this.pts[i].id === id){
                  this.pts[i].x = pos.x;
                  this.pts[i].y = pos.y;
                  break;
                }    
              }
            }
        
            // 绘制多边形
            this.draw = function (){  
              if (this.drawingPolygon){
                this.drawingPolygon.remove();
              }
              
              // 转换顶点格式为Konva.Polygon需要的数组
              var pointsArr = [];
              this.pts.forEach(pt => {
                pointsArr.push(pt.x, pt.y);
              });
        
              // 绘制闭合多边形
              this.drawingPolygon = new Konva.Polygon({
                  points: pointsArr,
                  stroke: this.strokeColor,
                  strokeWidth: 2,
                  fill: this.fillColor,
                  fillOpacity: 0.3
                });
        
              this.polygonLayer.add(this.drawingPolygon);
              this.ptLayer.draw();
              this.polygonLayer.draw();
            }
        
            // 导出顶点坐标
            this.exportPoints = function(){
              var list = [];    
              for (var i = 0; i < this.pts.length; i++){      
                var pt = this.pts[i];
                if (pt.fixed){
                  list.push({x: pt.x, y: pt.y});   
                }   
              }  
              return list;
            }
            
            // 重置画布
            this.reset = function(){
                this.pts.forEach(pt => pt.kill());
                this.pts = [];
                if (this.drawingPolygon){
                    this.drawingPolygon.remove();
                }
                this.polygonLayer.draw();
                this.ptLayer.draw();
            }
        }
        
        var polygonPath = new PolygonPath();
        polygonPath.polygonLayer = polygonLayer;
        polygonPath.ptLayer = pointLayer;
        
        polygonPath.fillColor = 'AliceBlue'; 
        polygonPath.strokeColor = 'blue'; 
        polygonPath.pointOpacity = 1;
        
        // 点击画布添加顶点
        s1.on('mouseup touchend', function () {
          polygonPath.addPt(s1.getPointerPosition(), true);
        });
        
        // 导出按钮事件
        document.getElementById('export').addEventListener('click', function(){
          const btn = this;
          const textarea = document.getElementById('points');
          if (btn.textContent === "导出坐标"){
            btn.textContent = '隐藏';
            textarea.style.display = 'block';
            textarea.value = JSON.stringify(polygonPath.exportPoints());
          } else {
            btn.textContent = '导出坐标';
            textarea.style.display = 'none';
          }  
        })
        
        // 重置按钮事件
        document.getElementById('reset').addEventListener('click', function(){
          polygonPath.reset();
        })
    </script>
</body>
</html>

关键功能说明

1. 网格吸附实现

  • 定义GRID_SIZE配置网格大小,画布背景添加网格线辅助可视化
  • 实现snapToGrid函数,将坐标按网格大小取整对齐
  • 在添加顶点、拖拽顶点的全流程中,对坐标进行吸附处理,确保所有顶点都落在网格节点上

2. 多边形可编辑功能

  • 使用Konva.Polygon代替原有的Konva.Line,自动生成闭合路径
  • 保留拖拽顶点逻辑,拖拽任意顶点即可修改多边形的形状和尺寸,拖拽过程中坐标自动吸附网格
  • 添加重置按钮,支持清空画布重新绘制

内容的提问来源于stack exchange,提问作者Anthony Wratten

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 06:17:05