javascript - 需要帮助将织物 Canvas 的代码从 Vanilla JS 转换为 ReactJS

标签 javascript html reactjs canvas fabricjs

所以我在玩 fabricjs canvas 库,我找到了 this fiddle用 vanillajs 编写,可让您在 Canvas 上绘制多边形。我想在我的 React 项目中实现这个确切的东西,所以我试图将整个代码转换为 React ( https://codesandbox.io/s/jolly-kowalevski-tjt58 )。该代码有些工作,但有一些新错误不在原始 fiddle 中,我无法修复它们。

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

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

额外信息:
fiddle 中使用的 Fabric 版本: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. 使用useState对于 prevCords 是一种浪费,因为 React 将在每个 setState 上重新渲染

关于javascript - 需要帮助将织物 Canvas 的代码从 Vanilla JS 转换为 ReactJS,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67660697/

相关文章:

javascript - D3 从堆叠条形图到条形图的转换仅在第一次有效

javascript - 将输入字段的两个值传递给脚本并合并以进行搜索

javascript - Meteor 中的延迟状态检查

javascript - 在 React.js 中 data-reactid 破坏了 html 文本输入?

javascript - 在函数中传递多个回调

javascript - 如何为我的模型使用 ng-bind?

html - 两个绝对 -ley 定位元素之间的边距

html - 如何使标签式导航流畅

javascript - 取消选中复选框时触发事件

javascript - 如何将原始 JSX 标记传递给组件以作为代码显示在页面中?