首页 > 解决方案 > 需要帮助将织物画布的代码从 Vanilla JS 转换为 ReactJS

问题描述

所以我在玩 fabricjs 画布库,我发现这个小提琴是用 vanillajs 写的,它可以让你在画布上绘制多边形。我想在我的 react 项目中实现这个确切的东西,所以我尝试将整个代码转换为 react ( https://codesandbox.io/s/jolly-kowalevski-tjt58 )。该代码有些工作,但有一些新的错误不在原始小提琴中,我在修复它们时遇到了麻烦。

例如:尝试通过单击绘制按钮创建一个多边形,当你第一次这样做时,多边形被绘制没有任何错误,但是当你第二次再次单击绘制按钮时,画布开始表现得很奇怪而且很奇怪创建多边形。

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

额外信息:
小提琴中使用的
面料版本: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);
};

标签: javascripthtmlreactjscanvasfabricjs

解决方案


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

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. 使用useStateprevCords 是一种浪费,因为 React 会在每个 setState 上重新渲染

推荐阅读