需要帮助将 fabric canvas 的代码从 Vanilla JS 转换为 ReactJS

Need help converting code for fabric canvas from Vanilla JS to ReactJS

所以我在玩 fabricjs canvas 库,我发现 this fiddle written in vanillajs which lets you draw polygons on the canvas. I wanted to implement this exact thing in my react project so I tried to convert the entire code into react (https://codesandbox.io/s/jolly-kowalevski-tjt58)。该代码有些工作,但有一些新错误不在原始 fiddle 中,我无法修复它们。

例如:尝试通过单击绘制按钮创建多边形,当您第一次这样做时,绘制的多边形没有任何错误,但是当您第二次单击绘制按钮时,canvas 开始表现得很奇怪,并创建了一个奇怪的多边形。

所以基本上我需要帮助来转换原始代码以应对 0 个错误。

补充信息:
fiddle中使用的结构版本:4.0.0
沙盒中的结构版本:4.0.0

香草 Js 代码:

const getPathBtn = document.getElementById("get-path");
const drawPolygonBtn = document.getElementById("draw-polygon");
const showPolygonBtn = document.getElementById("show-polygon");
const editPolygonBtn = document.getElementById("edit-polygon");

const canvas = new fabric.Canvas("canvas", {
  selection: false
});

let line, isDown;
let prevCords;
let vertices = [];
let polygon;

const resetCanvas = () => {
  canvas.off();
  canvas.clear();
};

const resetVariables = () => {
  line = undefined;
  isDown = undefined;
  prevCords = undefined;
  polygon = undefined;
  vertices = [];
};

const addVertice = (newPoint) => {
  if (vertices.length > 0) {
    const lastPoint = vertices[vertices.length - 1];
    if (lastPoint.x !== newPoint.x && lastPoint.y !== newPoint.y) {
      vertices.push(newPoint);
    }
  } else {
    vertices.push(newPoint);
  }
};

const drawPolygon = () => {
  resetVariables();
  resetCanvas();

  canvas.on("mouse:down", function(o) {
    isDown = true;
    const pointer = canvas.getPointer(o.e);

    let points = [pointer.x, pointer.y, pointer.x, pointer.y];

    if (prevCords && prevCords.x2 && prevCords.y2) {
      const prevX = prevCords.x2;
      const prevY = prevCords.y2;
      points = [prevX, prevY, prevX, prevY];
    }

    const newPoint = {
      x: points[0],
      y: points[1]
    };
    addVertice(newPoint);

    line = new fabric.Line(points, {
      strokeWidth: 2,
      fill: "black",
      stroke: "black",
      originX: "center",
      originY: "center",
    });
    canvas.add(line);
  });

  canvas.on("mouse:move", function(o) {
    if (!isDown) return;
    const pointer = canvas.getPointer(o.e);
    const coords = {
      x2: pointer.x,
      y2: pointer.y
    };
    line.set(coords);
    prevCords = coords;
    canvas.renderAll();
  });

  canvas.on("mouse:up", function(o) {
    isDown = false;

    const pointer = canvas.getPointer(o.e);

    const newPoint = {
      x: pointer.x,
      y: pointer.y
    };
    addVertice(newPoint);
  });

  canvas.on("object:moving", function(option) {
    const object = option.target;
    canvas.forEachObject(function(obj) {
      if (obj.name == "Polygon") {
        if (obj.PolygonNumber == object.polygonNo) {
          const points = window["polygon" + object.polygonNo].get(
            "points"
          );
          points[object.circleNo - 1].x = object.left;
          points[object.circleNo - 1].y = object.top;
          window["polygon" + object.polygonNo].set({
            points: points,
          });
        }
      }
    });
    canvas.renderAll();
  });
};

const showPolygon = () => {
  resetCanvas();

  if (!polygon) {
    polygon = new fabric.Polygon(vertices, {
      fill: "transparent",
      strokeWidth: 2,
      stroke: "black",
      objectCaching: false,
      transparentCorners: false,
      cornerColor: "blue",
    });
  }

  polygon.edit = false;
  polygon.hasBorders = true;
  polygon.cornerColor = "blue";
  polygon.cornerStyle = "rect";
  polygon.controls = fabric.Object.prototype.controls;
  canvas.add(polygon);
};

// polygon stuff

// define a function that can locate the controls.
// this function will be used both for drawing and for interaction.
function polygonPositionHandler(dim, finalMatrix, fabricObject) {
  let 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) {
  let 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) {
    let 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() {
  canvas.setActiveObject(polygon);

  polygon.edit = true;
  polygon.hasBorders = false;

  let lastControl = polygon.points.length - 1;
  polygon.cornerStyle = "circle";
  polygon.cornerColor = "rgba(0,0,255,0.5)";
  polygon.controls = polygon.points.reduce(function(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;
  }, {});

  canvas.requestRenderAll();
}

// Button events

drawPolygonBtn.onclick = () => {
  drawPolygon();
};

showPolygonBtn.onclick = () => {
  showPolygon();
};

editPolygonBtn.onclick = () => {
  editPolygon();
};

getPathBtn.onclick = () => {
  console.log("vertices", polygon.points);
};

在第二次绘制时(第二次再次单击绘制按钮),线始终连接到同一点。所以prevCords有问题。 通过将 console.log 添加到 "mouse:mouse" 的处理函数中确认了上面的语句:

fabricCanvas.on("mouse:move", function (o) {
  console.log("mousemove fired", prevCords); // always the same value
  if (isDown.current || !line.current) return;
  const pointer = fabricCanvas.getPointer(o.e);
  const coords = {
    x2: pointer.x,
    y2: pointer.y
  };
  line.current.set(coords);
  setPrevCords(coords); // the line should connect to this new point
  fabricCanvas.renderAll();
});

这是因为 closure,mouse:move 的函数处理程序将始终记住 prevCords 创建时的值(即当您单击 Draw 按钮时),而不是更新的值setPrevCords

要解决上述问题,只需使用useRef存储prevCords(或使用参考) 第 6 行:

  const [fabricCanvas, setFabricCanvas] = useState();
  const prevCordsRef = useRef();
  const line = useRef();

第 35 行:

  const resetVariables = () => {
    line.current = undefined;
    isDown.current = undefined;
    prevCordsRef.current = undefined;
    polygon.current = undefined;
    vertices.current = [];
  };

第 65 行:

  if (prevCordsRef.current && prevCordsRef.current.x2 && prevCordsRef.current.y2) {
    const prevX = prevCordsRef.current.x2;
    const prevY = prevCordsRef.current.y2;
    points = [prevX, prevY, prevX, prevY];
  }

第 96 行:

  prevCordsRef.current = coords;

最后一个建议是更改第 89 行(因此功能与演示匹配):

  if (!isDown.current) return;

总结:

  1. 不要对必须在另一个函数处理程序中具有最新值的变量使用 useState。使用 useRef 代替
  2. 对 prevCords 使用 useState 是一种浪费,因为 React 将在每个 setState 上重新渲染