【问题标题】:FabricJS How can I update the custom control point of a polygon after Zoom or Pan the canvas?FabricJS 如何在缩放或平移画布后更新多边形的自定义控制点?
【发布时间】:2020-06-20 03:30:34
【问题描述】:

有人可以建议我在缩放或平移画布后更新自定义多边形的变换点和自定义控制点(通过鼠标移动+单击绘制)的正确方法吗?

重现步骤:

  1. 点击绘制多边形

  2. 在画布中绘制多边形

  3. 缩放(向上或向下滚动)或平移(alt + 鼠标拖动)

这是我的代码:https://jsfiddle.net/ckitisak/qv6y283p/

    /**
     * based on:
     * 1. https://codepen.io/durga598/pen/gXQjdw?editors=0010
     * 2. http://fabricjs.com/custom-controls-polygon
     */


    let activeLine;
    let activeShape;
    let canvas;
    let lineArray = [];
    let pointArray = [];
    let drawMode = false;

    function initCanvas() {
        canvas = new fabric.Canvas('c');
        canvas.backgroundColor = 'rgba(0,0,0,0.3)';
        canvas.setHeight(500);
        canvas.setWidth(500);

        fabric.Object.prototype.originX = 'center';
        fabric.Object.prototype.originY = 'center';

        canvas.on('mouse:down', onMouseDown);
        canvas.on('mouse:up', onMouseUp);
        canvas.on('mouse:move', onMouseMove);
        canvas.on('object:moving', onObjectMove);
        canvas.on('mouse:wheel', onMouseWheel);
    }

    function onMouseDown(options) {    
        if (drawMode) {
            if (options.target && options.target.id === pointArray[0].id) {
                // when click on the first point
                generatePolygon(pointArray);
            } else {
                addPoint(options);
            }
        }
        
        var evt = options.e;
        if (evt.altKey === true) {
            this.isDragging = true;
            this.selection = false;
            this.lastPosX = evt.clientX;
            this.lastPosY = evt.clientY;
        }
    }

    function onMouseUp(options) {
      this.isDragging = false;
      this.selection = true;
    }

    function onMouseMove(options) {
    	if (this.isDragging) {
        	var e = options.e;
        	this.viewportTransform[4] += e.clientX - this.lastPosX;
            this.viewportTransform[5] += e.clientY - this.lastPosY;
            this.requestRenderAll();
            this.lastPosX = e.clientX;
            this.lastPosY = e.clientY;
        } 
        if (drawMode) {
            if (activeLine && activeLine.class === 'line') {
                const pointer = canvas.getPointer(options.e);
                activeLine.set({
                    x2: pointer.x,
                    y2: pointer.y
                });
                const points = activeShape.get('points');
                points[pointArray.length] = {
                    x: pointer.x,
                    y: pointer.y,
                };
                activeShape.set({
                    points
                });
            }
            canvas.renderAll();
        }
    }

    function onMouseWheel(options) {
      var delta = options.e.deltaY;
      var pointer = canvas.getPointer(options.e);
      var zoom = canvas.getZoom();  
      if (delta > 0) {
      	zoom += 0.1;
      } else {
      	zoom -= 0.1;
      }
      if (zoom > 20) zoom = 20;
      if (zoom < 0.1) zoom = 0.1;
      canvas.zoomToPoint({ x: options.e.offsetX, y: options.e.offsetY }, zoom);
      options.e.preventDefault();
      options.e.stopPropagation();
    }

    function onObjectMove(option) {
        const object = option.target;
        object._calcDimensions();
        object.setCoords();    
        canvas.renderAll();
    }

    function toggleDrawPolygon(event) {
        if (drawMode) {
            // stop draw mode
            activeLine = null;
            activeShape = null;
            lineArray = [];
            pointArray = [];
            canvas.selection = true;
            drawMode = false;
        } else {
            // start draw mode
            canvas.selection = false;
            drawMode = true;
        }
    }

    function addPoint(options) {
        const pointOption = {
            id: new Date().getTime(),
            radius: 5,
            fill: '#ffffff',
            stroke: '#333333',
            strokeWidth: 0.5,
            left: options.e.layerX / canvas.getZoom(),
            top: options.e.layerY / canvas.getZoom(),
            selectable: false,
            hasBorders: false,
            hasControls: false,
            originX: 'center',
            originY: 'center',
            objectCaching: false,
        };
        const point = new fabric.Circle(pointOption);

        if (pointArray.length === 0) {
            // fill first point with red color
            point.set({
                fill: 'red'
            });
        }

        const linePoints = [
            options.e.layerX / canvas.getZoom(),
            options.e.layerY / canvas.getZoom(),
            options.e.layerX / canvas.getZoom(),
            options.e.layerY / canvas.getZoom(),
        ];
        const lineOption = {
            strokeWidth: 2,
            fill: '#999999',
            stroke: '#999999',
            originX: 'center',
            originY: 'center',
            selectable: false,
            hasBorders: false,
            hasControls: false,
            evented: false,
            objectCaching: false,
        };
        const line = new fabric.Line(linePoints, lineOption);
        line.class = 'line';

        if (activeShape) {
            const pos = canvas.getPointer(options.e);
            const points = activeShape.get('points');
            points.push({
                x: pos.x,
                y: pos.y
            });
            const polygon = new fabric.Polygon(points, {
                stroke: '#333333',
                strokeWidth: 1,
                fill: '#cccccc',
                opacity: 0.3,
                selectable: false,
                hasBorders: false,
                hasControls: false,
                evented: false,
                objectCaching: false,
            });
            canvas.remove(activeShape);
            canvas.add(polygon);
            activeShape = polygon;
            canvas.renderAll();
        } else {
            const polyPoint = [{
                x: options.e.layerX / canvas.getZoom(),
                y: options.e.layerY / canvas.getZoom(),
            }, ];
            const polygon = new fabric.Polygon(polyPoint, {
                stroke: '#333333',
                strokeWidth: 1,
                fill: '#cccccc',
                opacity: 0.3,
                selectable: false,
                hasBorders: false,
                hasControls: false,
                evented: false,
                objectCaching: false,
            });
            activeShape = polygon;
            canvas.add(polygon);
        }

        activeLine = line;
        pointArray.push(point);
        lineArray.push(line);

        canvas.add(line);
        canvas.add(point);
    }

    function generatePolygon(pointArray) {
        const points = [];
        // collect points and remove them from canvas
        for (const point of pointArray) {
            points.push({
                x: point.left,
                y: point.top,
            });
            canvas.remove(point);
        }

        // remove lines from canvas
        for (const line of lineArray) {
            canvas.remove(line);
        }

        // remove selected Shape and Line 
        canvas.remove(activeShape).remove(activeLine);

        // create polygon from collected points
        const polygon = new fabric.Polygon(points, {
            id: new Date().getTime(),
            stroke: '#eee',
            fill: '#f00',
            objectCaching: false,
            moveable: false,
            //selectable: false
        });
        canvas.add(polygon);

        toggleDrawPolygon();
        editPolygon();
    }

    /**
     * define a function that can locate the controls.
     * this function will be used both for drawing and for interaction.
     */
    function polygonPositionHandler(dim, finalMatrix, fabricObject) {
        const transformPoint = {
            x: fabricObject.points[this.pointIndex].x - fabricObject.pathOffset.x,
            y: fabricObject.points[this.pointIndex].y - fabricObject.pathOffset.y,
        };
        return fabric.util.transformPoint(transformPoint, fabricObject.calcTransformMatrix());
    }

    /**
     * define a function that will define what the control does
     * this function will be called on every mouse move after a control has been
     * clicked and is being dragged.
     * The function receive as argument the mouse event, the current trasnform object
     * and the current position in canvas coordinate
     * transform.target is a reference to the current object being transformed,
     */
    function actionHandler(eventData, transform, x, y) {
        const polygon = transform.target;
        const currentControl = polygon.controls[polygon.__corner];
        const mouseLocalPosition = polygon.toLocalPoint(new fabric.Point(x, y), 'center', 'center');
        const size = polygon._getTransformedDimensions(0, 0);
        const finalPointPosition = {
            x: (mouseLocalPosition.x * polygon.width) / size.x + polygon.pathOffset.x,
            y: (mouseLocalPosition.y * polygon.height) / size.y + polygon.pathOffset.y,
        };
        polygon.points[currentControl.pointIndex] = finalPointPosition;
        return true;
    }

    /**
     * define a function that can keep the polygon in the same position when we change its
     * width/height/top/left.
     */
    function anchorWrapper(anchorIndex, fn) {
        return function(eventData, transform, x, y) {
            const fabricObject = transform.target;
            const point = {
                x: fabricObject.points[anchorIndex].x - fabricObject.pathOffset.x,
                y: fabricObject.points[anchorIndex].y - fabricObject.pathOffset.y,
            };

            // update the transform border
            fabricObject._setPositionDimensions({});

            // Now newX and newY represent the point position with a range from
            // -0.5 to 0.5 for X and Y.
            const newX = point.x / fabricObject.width;
            const newY = point.y / fabricObject.height;

            // Fabric supports numeric origins for objects with a range from 0 to 1.
            // This let us use the relative position as an origin to translate the old absolutePoint.
            const absolutePoint = fabric.util.transformPoint(point, fabricObject.calcTransformMatrix());
            fabricObject.setPositionByOrigin(absolutePoint, newX + 0.5, newY + 0.5);

            // action performed
            return fn(eventData, transform, x, y);
        };
    }

    function editPolygon() {
        let activeObject = canvas.getActiveObject();
        if (!activeObject) {
            activeObject = canvas.getObjects()[0];
            canvas.setActiveObject(activeObject);
        }

        activeObject.edit = true;
        activeObject.objectCaching = false;

        const lastControl = activeObject.points.length - 1;
        activeObject.cornerStyle = 'circle';
        activeObject.controls = activeObject.points.reduce((acc, point, index) => {
            acc['p' + index] = new fabric.Control({
                positionHandler: polygonPositionHandler,
                actionHandler: anchorWrapper(index > 0 ? index - 1 : lastControl, actionHandler),
                actionName: 'modifyPolygon',
                pointIndex: index,
            });
            return acc;
        }, {});

        activeObject.hasBorders = false;

        canvas.requestRenderAll();
    }

    function resizePolygon() {
        let activeObject = canvas.getActiveObject();
        if (!activeObject) {
            activeObject = canvas.getObjects()[0];
            canvas.setActiveObject(activeObject);
        }

        activeObject.edit = false;
        activeObject.objectCaching = false;
        activeObject.controls = fabric.Object.prototype.controls;
        activeObject.cornerStyle = 'rect';
        activeObject.hasBorders = true;

        canvas.requestRenderAll();
    }

    initCanvas();
    <script src="https://unpkg.com/fabric@4.0.0-beta.12/dist/fabric.js"></script>

    <button type="button" onclick="toggleDrawPolygon()">Draw Polygon</button>
    <button type="button" onclick="editPolygon()">Edit Polygon</button>
    <button type="button" onclick="resizePolygon()">Resize/Move Polygon</button>

    <canvas id="c"></canvas>

【问题讨论】:

    标签: javascript fabricjs


    【解决方案1】:

    我修改了代码以考虑画布缩放和平移。

    在计算控件位置时需要考虑视口变换。

    /**
         * based on:
         * 1. https://codepen.io/durga598/pen/gXQjdw?editors=0010
         * 2. http://fabricjs.com/custom-controls-polygon
         */
    
    
        let activeLine;
        let activeShape;
        let canvas;
        let lineArray = [];
        let pointArray = [];
        let drawMode = false;
    
        function initCanvas() {
            canvas = new fabric.Canvas('c');
            canvas.backgroundColor = 'rgba(0,0,0,0.3)';
            canvas.setHeight(500);
            canvas.setWidth(500);
    
            fabric.Object.prototype.originX = 'center';
            fabric.Object.prototype.originY = 'center';
    
            canvas.on('mouse:down', onMouseDown);
            canvas.on('mouse:up', onMouseUp);
            canvas.on('mouse:move', onMouseMove);
            canvas.on('object:moving', onObjectMove);
            canvas.on('mouse:wheel', onMouseWheel);
        }
    
        function onMouseDown(options) {    
            if (drawMode) {
                if (options.target && options.target.id === pointArray[0].id) {
                    // when click on the first point
                    generatePolygon(pointArray);
                } else {
                    addPoint(options);
                }
            }
            
            var evt = options.e;
            if (evt.altKey === true) {
                this.isDragging = true;
                this.selection = false;
                this.lastPosX = evt.clientX;
                this.lastPosY = evt.clientY;
            }
        }
    
        function onMouseUp(options) {
          this.isDragging = false;
          this.selection = true;
        }
    
        function onMouseMove(options) {
        	if (this.isDragging) {
            	var e = options.e;
            	this.viewportTransform[4] += e.clientX - this.lastPosX;
                this.viewportTransform[5] += e.clientY - this.lastPosY;
                this.requestRenderAll();
                this.lastPosX = e.clientX;
                this.lastPosY = e.clientY;
            } 
            if (drawMode) {
                if (activeLine && activeLine.class === 'line') {
                    const pointer = canvas.getPointer(options.e);
                    activeLine.set({
                        x2: pointer.x,
                        y2: pointer.y
                    });
                    const points = activeShape.get('points');
                    points[pointArray.length] = {
                        x: pointer.x,
                        y: pointer.y,
                    };
                    activeShape.set({
                        points
                    });
                }
                canvas.renderAll();
            }
        }
    
        function onMouseWheel(options) {
          var delta = options.e.deltaY;
          var pointer = canvas.getPointer(options.e);
          var zoom = canvas.getZoom();  
          if (delta > 0) {
          	zoom += 0.1;
          } else {
          	zoom -= 0.1;
          }
          if (zoom > 20) zoom = 20;
          if (zoom < 0.1) zoom = 0.1;
          canvas.zoomToPoint({ x: options.e.offsetX, y: options.e.offsetY }, zoom);
          options.e.preventDefault();
          options.e.stopPropagation();
        }
    
        function onObjectMove(option) {
            const object = option.target;
            object._calcDimensions();
            object.setCoords();    
            canvas.renderAll();
        }
    
        function toggleDrawPolygon(event) {
            if (drawMode) {
                // stop draw mode
                activeLine = null;
                activeShape = null;
                lineArray = [];
                pointArray = [];
                canvas.selection = true;
                drawMode = false;
            } else {
                // start draw mode
                canvas.selection = false;
                drawMode = true;
            }
        }
    
        function addPoint(options) {
            const pointOption = {
                id: new Date().getTime(),
                radius: 5,
                fill: '#ffffff',
                stroke: '#333333',
                strokeWidth: 0.5,
                left: options.e.layerX / canvas.getZoom(),
                top: options.e.layerY / canvas.getZoom(),
                selectable: false,
                hasBorders: false,
                hasControls: false,
                originX: 'center',
                originY: 'center',
                objectCaching: false,
            };
            const point = new fabric.Circle(pointOption);
    
            if (pointArray.length === 0) {
                // fill first point with red color
                point.set({
                    fill: 'red'
                });
            }
    
            const linePoints = [
                options.e.layerX / canvas.getZoom(),
                options.e.layerY / canvas.getZoom(),
                options.e.layerX / canvas.getZoom(),
                options.e.layerY / canvas.getZoom(),
            ];
            const lineOption = {
                strokeWidth: 2,
                fill: '#999999',
                stroke: '#999999',
                originX: 'center',
                originY: 'center',
                selectable: false,
                hasBorders: false,
                hasControls: false,
                evented: false,
                objectCaching: false,
            };
            const line = new fabric.Line(linePoints, lineOption);
            line.class = 'line';
    
            if (activeShape) {
                const pos = canvas.getPointer(options.e);
                const points = activeShape.get('points');
                points.push({
                    x: pos.x,
                    y: pos.y
                });
                const polygon = new fabric.Polygon(points, {
                    stroke: '#333333',
                    strokeWidth: 1,
                    fill: '#cccccc',
                    opacity: 0.3,
                    selectable: false,
                    hasBorders: false,
                    hasControls: false,
                    evented: false,
                    objectCaching: false,
                });
                canvas.remove(activeShape);
                canvas.add(polygon);
                activeShape = polygon;
                canvas.renderAll();
            } else {
                const polyPoint = [{
                    x: options.e.layerX / canvas.getZoom(),
                    y: options.e.layerY / canvas.getZoom(),
                }, ];
                const polygon = new fabric.Polygon(polyPoint, {
                    stroke: '#333333',
                    strokeWidth: 1,
                    fill: '#cccccc',
                    opacity: 0.3,
                    selectable: false,
                    hasBorders: false,
                    hasControls: false,
                    evented: false,
                    objectCaching: false,
                });
                activeShape = polygon;
                canvas.add(polygon);
            }
    
            activeLine = line;
            pointArray.push(point);
            lineArray.push(line);
    
            canvas.add(line);
            canvas.add(point);
        }
    
        function generatePolygon(pointArray) {
            const points = [];
            // collect points and remove them from canvas
            for (const point of pointArray) {
                points.push({
                    x: point.left,
                    y: point.top,
                });
                canvas.remove(point);
            }
    
            // remove lines from canvas
            for (const line of lineArray) {
                canvas.remove(line);
            }
    
            // remove selected Shape and Line 
            canvas.remove(activeShape).remove(activeLine);
    
            // create polygon from collected points
            const polygon = new fabric.Polygon(points, {
                id: new Date().getTime(),
                stroke: '#eee',
                fill: '#f00',
                objectCaching: false,
                moveable: false,
                //selectable: false
            });
            canvas.add(polygon);
    
            toggleDrawPolygon();
            editPolygon();
        }
    
        /**
         * define a function that can locate the controls.
         * this function will be used both for drawing and for interaction.
         */
    	function polygonPositionHandler(dim, finalMatrix, fabricObject) {
    	  var x = (fabricObject.points[this.pointIndex].x - fabricObject.pathOffset.x),
    		    y = (fabricObject.points[this.pointIndex].y - fabricObject.pathOffset.y);
    		return fabric.util.transformPoint(
    			{ x: x, y: y },
          fabric.util.multiplyTransformMatrices(
            fabricObject.canvas.viewportTransform,
            fabricObject.calcTransformMatrix()
          )
    		);
    	}
    
        /**
         * define a function that will define what the control does
         * this function will be called on every mouse move after a control has been
         * clicked and is being dragged.
         * The function receive as argument the mouse event, the current trasnform object
         * and the current position in canvas coordinate
         * transform.target is a reference to the current object being transformed,
         */
    	function actionHandler(eventData, transform, x, y) {
    		var polygon = transform.target,
    		    currentControl = polygon.controls[polygon.__corner],
    		    mouseLocalPosition = polygon.toLocalPoint(new fabric.Point(x, y), 'center', 'center'),
            polygonBaseSize = polygon._getNonTransformedDimensions(),
    				size = polygon._getTransformedDimensions(0, 0),
    				finalPointPosition = {
    					x: mouseLocalPosition.x * polygonBaseSize.x / size.x + polygon.pathOffset.x,
    					y: mouseLocalPosition.y * polygonBaseSize.y / size.y + polygon.pathOffset.y
    				};
    		polygon.points[currentControl.pointIndex] = finalPointPosition;
    		return true;
    	}
    
        /**
         * define a function that can keep the polygon in the same position when we change its
         * width/height/top/left.
         */
      function anchorWrapper(anchorIndex, fn) {
        return function(eventData, transform, x, y) {
          var fabricObject = transform.target,
              absolutePoint = fabric.util.transformPoint({
                  x: (fabricObject.points[anchorIndex].x - fabricObject.pathOffset.x),
                  y: (fabricObject.points[anchorIndex].y - fabricObject.pathOffset.y),
              }, fabricObject.calcTransformMatrix()),
              actionPerformed = fn(eventData, transform, x, y),
              newDim = fabricObject._setPositionDimensions({}),
              polygonBaseSize = fabricObject._getNonTransformedDimensions(),
              newX = (fabricObject.points[anchorIndex].x - fabricObject.pathOffset.x) / polygonBaseSize.x,
      		    newY = (fabricObject.points[anchorIndex].y - fabricObject.pathOffset.y) / polygonBaseSize.y;
          fabricObject.setPositionByOrigin(absolutePoint, newX + 0.5, newY + 0.5);
          return actionPerformed;
        }
      }
    
        function editPolygon() {
            let activeObject = canvas.getActiveObject();
            if (!activeObject) {
                activeObject = canvas.getObjects()[0];
                canvas.setActiveObject(activeObject);
            }
    
            activeObject.edit = true;
            activeObject.objectCaching = false;
    
            const lastControl = activeObject.points.length - 1;
            activeObject.cornerStyle = 'circle';
            activeObject.controls = activeObject.points.reduce((acc, point, index) => {
                acc['p' + index] = new fabric.Control({
                    positionHandler: polygonPositionHandler,
                    actionHandler: anchorWrapper(index > 0 ? index - 1 : lastControl, actionHandler),
                    actionName: 'modifyPolygon',
                    pointIndex: index,
                });
                return acc;
            }, {});
    
            activeObject.hasBorders = false;
    
            canvas.requestRenderAll();
        }
    
        function resizePolygon() {
            let activeObject = canvas.getActiveObject();
            if (!activeObject) {
                activeObject = canvas.getObjects()[0];
                canvas.setActiveObject(activeObject);
            }
    
            activeObject.edit = false;
            activeObject.objectCaching = false;
            activeObject.controls = fabric.Object.prototype.controls;
            activeObject.cornerStyle = 'rect';
            activeObject.hasBorders = true;
    
            canvas.requestRenderAll();
        }
    
        initCanvas();
    <script src="https://unpkg.com/fabric@4.0.0-beta.12/dist/fabric.js"></script>
    
        <button type="button" onclick="toggleDrawPolygon()">Draw Polygon</button>
        <button type="button" onclick="editPolygon()">Edit Polygon</button>
        <button type="button" onclick="resizePolygon()">Resize/Move Polygon</button>
    
        <canvas id="c"></canvas>

    【讨论】:

    • 嘿,我注意到在缩放后绘制一个新多边形时,它的锚点位于不同的位置。我试图解决这个问题,但找不到问题。请帮忙!
    猜你喜欢
    • 2017-12-20
    • 2022-09-29
    • 2019-12-17
    • 2015-05-05
    • 1970-01-01
    • 2016-08-26
    • 1970-01-01
    • 2016-11-11
    • 2015-08-08
    相关资源
    最近更新 更多