Fabric.js:移动多边形节点后边界框不更新问题及解决
Fabric.js 多边形节点移动后边界框不更新的解决方法
问题说明
移动Fabric.js多边形(polygon)的节点(node)时,边界框(bounding box)无法自动更新以包围所有新的点。尝试过站内所有矩阵操作、setCoords()等方法,耗时多日仍未解决;Fabric官方演示中多边形翻转功能已失效3年多未修复。最初仅需用少量代码实现多边形节点移动、位置平移功能(示例未包含旋转和缩放)。
变通修复方案
已实现完整的多边形/折线(polyline)编辑功能,支持绘制、节点移动、平移、缩放、翻转、旋转。完整代码如下:
<!DOCTYPE html> <html lang="en" xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Fabric.js Polygon move,flip,scale,resize,rotate by Robert W. Stewart</title> <script type="text/javascript" src="https://code.jquery.com/jquery-3.7.1.min.js"></script> <script type="text/javascript" src="https://unpkg.com/fabric@latest/dist/fabric.js"></script> <script> $(document).ready(function() { var pts=[],polyShape,nodeNum=0,nodes=[],polyType,polyline,polygon,lastPt=1,poly,btn,isDown=false,mouse,zindex,redrawAll; let defaults = {objectCaching:false,preserveObjectStacking:true,perPixelTargetFind:true}//JSON ...spread syntax canvas = new fabric.Canvas($('canvas')[0],{...defaults,targetFindTolerance:10}); let Props = {objectCaching:false,hasControls:false,hasBorders:true,originX:'center',originY:'center', strokeUniform:true,borderOpacityWhenMoving:0,cornerColor:'magenta',cornerStyle:'circle',padding:10,borderDashArray:[4,4]}; ///////////////a///////////////////////////////////////////////////// MOUSE:DOWN canvas.on('mouse:down', function (e) { if (btn=='Polyline' || btn=='Polygon' && poly==true) { if (pts.length > 1) { pts.splice(-1,1); } //remove duplicate start pts polyline = new fabric.Polyline(pts,{...Props,name:'temp',fill:'',stroke:'black'}); canvas.add(polyline); polyline.points[pts.length] = {x: parseInt(mouse.x), y: parseInt(mouse.y)}; lastPt++; isDown=true; $('#debug').text(JSON.stringify(pts)); info(); } if (e.target && isDown==false && e.target.name=='poly') {clearNodes();} }); //////////////////////////////////////////////////////////////////// MOUSE:MOVE (NEW POLYLINES) canvas.on('mouse:move', function(e){ mouse = canvas.getPointer(e); if (poly==true && isDown) { polyline.points[lastPt-1] = {x: mouse.x, y: mouse.y}; canvas.renderAll(); } }); //////////////////////////////////////////////////////////////////// OBJECT:MOVING (NODES) canvas.on('object:moving', function (e) { if (e.target && e.target.name=='node') { polyShape.points[ e.target.nodeNum] = {x:e.target.getCenterPoint().x, y:e.target.getCenterPoint().y}; } }); //////////////////////////////////////////////////////////////////// MOUSE:DBLCLICK canvas.on('mouse:dblclick', function (e) { if (btn=='Polyline' || btn=='Polygon') { canvas.forEachObject(function(i) { if (i.name=='temp') { canvas.remove(i); } }); makePolygon(); addNodes(polyShape); canvas.add.apply(canvas,nodes); info(); //place Line nodes (drag node) } if (e.target && poly==false && (e.target.name=='poly'|| e.target.name=='node') ) { addNodes(polyShape); canvas.add.apply(canvas,nodes); info(); //replace with new nodes after dblclick canvas.discardActiveObject().renderAll(); } resetToolBar('cursor'); canvas.renderAll(); isDown=false; poly=false; lastPt=1; //finished so reset }); //////////////////////////////////////////////////////////////////// OBJECT:MODIFIED canvas.on('object:modified', function(e) { if (e.target && poly==false && (e.target.name=='poly' || e.target.name=='node')) { addNodes(polyShape); pts = []; //put the node points around poly, reset points polyShape.get('points').forEach((point, i) => { pts[i]={x:nodes[i].left,y:nodes[i].top}; }); polyShape.set({redraw:true}); //flag to delete polygon (no duplicates) zindex=canvas.getObjects().indexOf(polyShape) makePolygon(); polyShape.moveTo(zindex); addNodes(polyShape); canvas.add.apply(canvas,nodes); info(); } }); canvas.on('selection:created', function() { if (canvas.getActiveObjects().length>1) {redrawAll=true; clearNodes();} }); canvas.on('selection:cleared', function() { if (redrawAll==true) {redrawAll=false; redrawAllPolys();} }); //canvas.on('object:scaling,object:rotating,object:moving', function() {clearNodes();}) function clearNodes() { nodes.forEach((node, i)=>{canvas.remove(node);}) } function redrawAllPolys() { canvas.getObjects().forEach((obj, i) => { if (obj.name=='poly') { addNodes(obj); //put the node points around poly pts = []; //reset points obj.get('points').forEach((point, i) => { pts[i]={x:nodes[i].left,y:nodes[i].top}; }); obj.set({redraw:true}); //flag to delete polygon (no duplicates) zindex=canvas.getObjects().indexOf(obj) if (obj.pType) { polyType = obj.pType; } //polygon or polyline makePolygon(); obj.moveTo(zindex); } }); }; //////////////////////////////////////////////////////////////////// MAKE POLYGON function makePolygon() { canvas.getObjects().forEach((obj, i) => { if (obj.redraw==true) { canvas.setActiveObject(obj); canvas.remove(canvas.getActiveObject()); } }); polyShape = new fabric[polyType](pts,{...Props,name:'poly',redraw:null,objectCaching:false }); Fill = polyType=='Polygon' ? '#'+Math.random().toString(16).slice(-6) : ''; //random color polyShape.set({pType:polyType,hasControls:true,fill:Fill,alphaFill:1,stroke:'black',strokeWidth:1,}); canvas.add(polyShape); polyShape.on('mousedown', function(e) { polyShape = e.target; //current poly if (e.target.get('type') =='polyline') {polyType='Polyline'} else {polyType='Polygon'} }); }; //////////////////////////////////////////////////////////////////// ADD NODES function addNodes(polyShape) { clearNodes(); matrix = polyShape.calcTransformMatrix(); nodeNum=0; var transformedpts = polyShape.get('points').map(function(p){ return new fabric.Point( p.x - polyShape.pathOffset.x, p.y - polyShape.pathOffset.y); }).map(function(p){ return fabric.util.transformPoint(p, matrix); }); nodes = transformedpts.map(function(p){ return new fabric.Circle({...Props,name:'node',nodeNum:nodeNum++,left:p.x,top:p.y,radius:7,fill:'red',hoverCursor:'pointer',selectable:false,opacity:.8}); }); nodes.forEach((node, i) => { node.on('mouseover', function(e) { node.selectable = true; node.set('fill','green'); canvas.renderAll(); }); node.on('mouseout', function(e) { node.selectable = false; node.set('fill','red'); canvas.renderAll(); }); }); }; //////////////////////////////////////////////////////////////////// UI DISPLAY $('#toolBarTable').on('click', 'tr:first-child td', function(e) { resetToolBar(this.id); btn = $(this).text(); clearNodes(); if (btn=='Cursor') { polyType=''; poly=false; } if (btn=='Polyline') { polyType='Polyline'; poly=true; pts=[]; lastPt=1; } if (btn=='Polygon') { polyType='Polygon'; poly=true; pts=[]; lastPt=1; } }); function resetToolBar(id) { $('#toolBarTable td').css( {'background-color':'white','color':'black'} ); $('#'+id).css( {'background-color':'green','color':'white' }); btn='Cursor'; } //////////////////////////////////////////////////////////////////// HTML TABLE function info() { $('.info td').each(function (i) { if(i==0 && polyShape) {$(this).text('nodes:'+nodes.length)}; if(i==1 && pts) $(this).text('points:'+nodes.length); if(i==2) $(this).text('objects:'+canvas.getObjects().length); if(i==3 && polyShape) $(this).text('originX:'+parseInt(polyShape.left)); if(i==4 && polyShape) $(this).text('originY:'+parseInt(polyShape.top)); }) } }); //end ready </script> <style> * {user-select: none;} #toolBarTable { margin: 20px auto; border-collapse: collapse; font-family: Arial, Helvetica, sans-serif; width: 600px; } #toolBarTable tr:first-child td { padding: 4px; text-align: center; border: 1px black solid; min-width: 120px; cursor: pointer; } #td2 { max-width:240px; pointer-events: none; } #toolBarTable tr:first-child td:hover{ background-color: Gainsboro; } .info td { padding: 10px; } </style> </head> <body ondragstart="return false;" ondrop="return false;"> <table id="toolBarTable"> <tr> <td id="cursor">Cursor:</td> <td colspan="2" id="td2">dblclick poly for nodes</td> <td id="polyline">Polygon</td> <td id="polygon">Polyline</td> </tr> <tr> <td colspan="5"> <canvas id="canvas" width="600" height="400" style="border:1px solid black"></canvas></td> </tr> <tr class="info"> <td>nodes:</td> <td>points:</td> <td>objects:</td> <td>originX:</td> <td>originY:</td> </tr> <tr> <td colspan="5"> <div id="debug">double click to close polygon</div> </td> </tr> </table> </body> </html>
内容的提问来源于stack exchange,提问作者Robbie Stewart
相关产品推荐
相关产品推荐

