javascript - 需要帮助将织物画布的代码从 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);
};
解决方案
在第二次绘制(第二次再次单击绘制按钮)时,线始终连接到同一点。所以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;
总结:
- 不要
useState
用于在另一个函数处理程序中必须具有最新值的变量。useRef
改为使用 - 使用
useState
prevCords 是一种浪费,因为 React 会在每个 setState 上重新渲染
推荐阅读
- javascript - 如何在不刷新组件的情况下根据我的 API 中的数据重新路由 - React/Redux
- google-colaboratory - 有没有办法在 google colab 中显示所有依赖项及其版本?
- c# - 在给定的时间段之间每天更改应用程序的某些元素
- javascript - 以编程方式在 xterm js 中插入输入
- c# - c# 使用 out 时不必要的赋值
- mysql - 使用 Express 中的 Sequelize 中的方法创建检查表中存在的 ID
- java - 如何重现 ElasticSearch 查询?
- qnamaker - QnA MAker KB 的语言问题
- javascript - 从包含的元素中获取 iframe
- javascript - 使用下划线 _reduce 将数组转换为对象