Нужна помощь в преобразовании кода для тканевого холста из Vanilla JS в ReactJS

#javascript #html #reactjs #canvas #fabricjs

Вопрос:

итак, я поиграл с библиотекой холста 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 amp;amp; 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 amp;amp; prevCords.x2 amp;amp; 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);
};
 

Ответ №1:

При 2-м розыгрыше (нажмите кнопку розыгрыша еще раз во второй раз) линия всегда соединяется с одной и той же точкой. Таким образом, существует проблема с предварительными записями. Добавив консоль.войдите в функцию обработчика «мышь:мышь», подтвержденную приведенным выше утверждением:

 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 всегда будет помнить значение предварительных записей при его создании (т. е. при нажатии на кнопку рисования), а не значение, которое было обновлено setPrevCords

Чтобы решить вышеуказанную проблему, просто используйте useRef для хранения предварительных записей (или используйте ссылку) Строка 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 amp;amp; prevCordsRef.current.x2 amp;amp; 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 для предварительных записей бесполезно, так как React будет повторно отображаться при каждом заданном состоянии