首页 > 解决方案 > 移动后如何更新自定义形状多边形的变换位置

问题描述

绘制多边形后,如果我移动多边形上的任何点以重新塑造它。多边形的形状已正确更新,但如果我在更新多边形形状之前将多边形移动到画布上的任何位置,我的多边形会跳回到创建它的位置。请给我一些关于我遗漏或做错了什么的建议。谢谢你。

重现步骤

  1. 点击绘制多边形
  2. 画一个多边形
  3. 移动多边形
  4. 通过移动多边形上的点来重塑多边形

这是代码:https ://jsfiddle.net/ckitisak/3rwv1ae9

/**
 * 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:move', onMouseMove);
    canvas.on('object:moving', onObjectMove);
}

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);
        }
    }
}

function onMouseMove(options) {
    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 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,
    });
    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.cornerColor = '#f00';
    activeObject.cornerStrokeColor = '#000';
    activeObject.transparentCorners = false;
    activeObject.borderColor = '#000';
    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>

标签: javascriptfabricjs

解决方案


推荐阅读