这是一个非常奇怪的问题,我无法理解并把我先前发布的this issue关联起来。
用户可以在画布上绘制线条(路径)。绘制直线后,将在直线上定位3个锚点。用户应该能够拖动整条线来重新定位它,或者拖动任意一个锚点来更改起点,终点或二次曲线。
我遇到的问题是,当最初绘制路径时,其路径本身的位置已关闭,而锚点的位置却是正确的。如果再沿那条路径拖动,您会看到一切都保持在同一位置。
请注意,每次更改路径时,我都会重绘该路径,这是因为如果拖动任一锚点(example of issue here),则该行周围的边界框不会更新。我还需要确保保存画布后的所有值都是最新的,以便以后可以重新绘制。
我几乎可以确定这与该行的originX和originY有关。更改它的确有效果(如果从Line变量中注释originX:“ center”和originY:“ center”,则可以看到初始绘制是正确的(尽管以我不喜欢的奇怪方式绘制),但是锚的任何后续移动或重新定位都会导致画布周围跳动)。
冗长的代码段的歉意。
let canvas;
let line;
let lineAnchorStart;
let lineAnchorEnd;
let lineAnchorBend;
let activeItem;
let drawingModeOn = true;
const button = document.getElementById('toggle-drawing-mode');
const Line = fabric.util.createClass(fabric.Path, {
type: 'line',
initialize(element, options) {
options || (options = {});
this.callSuper('initialize', element, options);
// Set default options
this.set({
objectCaching: false,
hasControls: false,
// Comment out the below 2 lines
originX: 'center',
originY: 'center',
fill: 'transparent',
strokeWidth: 2,
stroke: 'black',
customProps: {
category: 'lines',
},
});
},
})
// Repositioning the line anchors after the line is moved or selected line is changed
const repositionLineAnchors = (line) => {
lineAnchorStart.set({
left: line.path[0][1],
top: line.path[0][2]
}).setCoords();
lineAnchorEnd.set({
left: line.path[1][3],
top: line.path[1][4]
}).setCoords();
// If the line is perfectly straight then we want to keep the bend anchor in the middle
// But if it has had bend applied to it then we let it stay where it was dragged
if ((line.path[1][1] === line.path[1][3]) && (line.path[1][2] === line.path[1][4])) {
const centerX = (line.path[0][1] + line.path[1][3]) / 2;
const centerY = (line.path[0][2] + line.path[1][4]) / 2;
lineAnchorBend.set({
left: centerX,
top: centerY
}).setCoords();
} else {
lineAnchorBend.set({
left: line.path[1][1],
top: line.path[1][2]
}).setCoords();
}
}
// If the line anchors themselves are moved the
const handleLineAnchorMove = (target) => {
switch (target.customProps.category) {
// Moving the line anchors
case 'line_anchor':
switch (target.customProps.type) {
case 'line_anchor_start':
activeItem.path[0][1] = target.left;
activeItem.path[0][2] = target.top;
activeItem.setCoords();
break;
case 'line_anchor_end':
// If the line is perfectly straight then we want to keep the quadratic value the same as the end point to avoid bending it
// But if it has had bend applied to it then the two can be treated separately
if ((activeItem.path[1][1] === activeItem.path[1][3]) && (activeItem.path[1][2] === activeItem.path[1][4])) {
activeItem.path[1][1] = target.left;
activeItem.path[1][2] = target.top;
}
activeItem.path[1][3] = target.left;
activeItem.path[1][4] = target.top;
activeItem.setCoords();
break;
case 'line_anchor_bend':
activeItem.path[1][1] = target.left;
activeItem.path[1][2] = target.top;
activeItem.setCoords();
break;
// no default
}
// no default
}
fabricCanvas.renderAll();
}
const transformedPoint = (target) => {
const points = [];
const path = target.path;
points.push(new fabric.Point(path[0][1], path[0][2]));
points.push(new fabric.Point(path[1][3], path[1][4]));
points.push(new fabric.Point(path[1][1], path[1][2]));
const matrix = target.calcTransformMatrix();
return points
.map(p => new fabric.Point(p.x - target.minX, p.y - target.minY))
.map(p => fabric.util.transformPoint(p, matrix));
}
const redrawPath = (oldLine) => {
const transformedPoints = transformedPoint(oldLine);
const path = [
[],
[]
];
path[0][0] = 'M';
path[0][1] = transformedPoints[0].x;
path[0][2] = transformedPoints[0].y;
path[1][0] = 'Q';
path[1][1] = transformedPoints[2].x;
path[1][2] = transformedPoints[2].y;
path[1][3] = transformedPoints[1].x;
path[1][4] = transformedPoints[1].y;
const newLine = drawLine(path);
repositionLineAnchors(newLine);
fabricCanvas.remove(oldLine).add(newLine).setActiveObject(newLine).renderAll();
}
const addLine = () => {
let isDown;
let startPoint;
fabricCanvas.on('mouse:down', (options) => {
hideLineAnchors();
isDown = true;
startPoint = fabricCanvas.getPointer(options.e);
const path = [
[],
[]
];
path[0][0] = 'M';
path[0][1] = startPoint.x;
path[0][2] = startPoint.y;
path[1][0] = 'Q';
path[1][1] = startPoint.x;
path[1][2] = startPoint.y;
path[1][3] = startPoint.x;
path[1][4] = startPoint.y;
line = drawLine(path);
line.selectable = false; // This is needed to prevent newly added lines from being dragged if drawing a line right next to them
fabricCanvas.add(line).renderAll();
});
fabricCanvas.on('mouse:move', (options) => {
if (!isDown) return;
const pointer = fabricCanvas.getPointer(options.e);
const lineWidth = pointer.x - startPoint.x;
const lineHeight = pointer.y - startPoint.y;
line.path[1][1] = pointer.x;
line.path[1][2] = pointer.y;
line.path[1][3] = pointer.x;
line.path[1][4] = pointer.y;
line.set({
height: Math.abs(lineHeight),
width: Math.abs(lineWidth)
}).setCoords();
lineAnchorEnd.set({
left: pointer.x,
top: pointer.y
});
fabricCanvas.renderAll();
});
fabricCanvas.on('mouse:up', (options) => {
isDown = false;
const endPoint = fabricCanvas.getPointer(options.e);
redrawPath(line);
disableDrawingMode();
});
}
const handleObjectSelected = (e) => {
let selectedItem = e.target;
switch (selectedItem.customProps.category) {
case 'line_anchor':
// If we select a line anchor we actually want the line to be the active object
selectedItem = activeItem;
disableDrawingMode();
break;
case 'lines':
repositionLineAnchors(selectedItem);
showLineAnchors();
fabricCanvas
.bringToFront(lineAnchorStart)
.bringToFront(lineAnchorEnd)
.bringToFront(lineAnchorBend)
.renderAll();
break;
}
activeItem = selectedItem;
}
const handleObjectMoving = (e) => {
const selectedItem = e.target;
// If not a group
if (selectedItem.customProps) {
switch (selectedItem.customProps.category) {
case 'line_anchor':
switch (selectedItem.customProps.type) {
case 'line_anchor_start':
case 'line_anchor_end':
lineAnchorBend.visible = false;
// no default
}
handleLineAnchorMove(selectedItem);
break;
case 'lines':
{
lineAnchorStart.visible === true && hideLineAnchors();
break;
}
// no default
}
}
}
const handleObjectModified = (e) => {
const selectedItem = e.target;
// If not a group
if (selectedItem.customProps) {
switch (selectedItem.customProps.category) {
case 'lines':
redrawPath(selectedItem);
showLineAnchors();
break;
case 'line_anchor':
redrawPath(activeItem);
showLineAnchors();
break;
// no default
}
}
}
const disableDrawingMode = () => {
drawingModeOn = false;
setButtonText();
fabricCanvas.selection = true;
fabricCanvas.forEachObject((object, i) => {
// This is to prevent the pitch background from being set to selectable (it is 0 in the object array)
if (i > 0) {
object.selectable = true;
}
});
fabricCanvas.defaultCursor = 'default';
fabricCanvas.hoverCursor = 'move';
// Remove event listeners
fabricCanvas
.off('mouse:down')
.off('mouse:move')
.off('mouse:up')
.off('mouse:out');
}
const enableDrawingMode = () => {
drawingModeOn = true;
setButtonText();
fabricCanvas.selection = false;
fabricCanvas.forEachObject((object) => {
object.selectable = false;
});
// Allow line anchors to be draggable while in drawing mode
lineAnchorStart.selectable = true;
lineAnchorEnd.selectable = true;
lineAnchorBend.selectable = true;
fabricCanvas.defaultCursor = 'crosshair';
fabricCanvas.hoverCursor = 'crosshair';
lineAnchorStart.hoverCursor = 'move';
lineAnchorEnd.hoverCursor = 'move';
lineAnchorBend.hoverCursor = 'move';
addLine();
}
const addLineAnchors = () => {
lineAnchorStart = createLineAnchor('line_anchor_start');
lineAnchorEnd = createLineAnchor('line_anchor_end');
lineAnchorBend = createLineAnchorBend('line_anchor_bend');
fabricCanvas.add(lineAnchorStart, lineAnchorEnd, lineAnchorBend);
}
const showLineAnchors = () => {
if (lineAnchorStart) {
lineAnchorStart.visible = true;
lineAnchorEnd.visible = true;
lineAnchorBend.visible = true;
}
}
const hideLineAnchors = () => {
if (lineAnchorStart) {
lineAnchorStart.visible = false;
lineAnchorEnd.visible = false;
lineAnchorBend.visible = false;
}
}
const createLineAnchor = anchorType => (
new fabric.Rect({
left: 0,
top: 0,
hasBorders: false,
hasControls: false,
originX: 'center',
originY: 'center',
height: 20,
width: 20,
strokeWidth: 2,
stroke: 'green',
fill: 'rgba(255, 255, 255, 0.1)',
visible: false,
excludeFromExport: true,
customProps: {
category: 'line_anchor',
type: anchorType,
},
})
)
const createLineAnchorBend = anchorType => (
new fabric.Circle({
left: 0,
top: 0,
hasBorders: false,
hasControls: false,
originX: 'center',
originY: 'center',
radius: 10,
strokeWidth: 2,
stroke: 'blue',
fill: 'rgba(63, 149, 220, 0.5)',
visible: false,
excludeFromExport: true,
customProps: {
category: 'line_anchor',
type: anchorType,
},
})
)
const setButtonText = () => {
if (drawingModeOn === true) {
button.textContent = 'Disable Drawing Mode';
} else {
button.textContent = 'Enable Drawing Mode';
}
}
const setDrawingMode = () => {
if (drawingModeOn === true) {
enableDrawingMode();
} else {
disableDrawingMode();
}
}
const initFabric = () => {
fabricCanvas = new fabric.Canvas('c', {
height: 1000,
width: 1000,
targetFindTolerance: 15,
selection: false,
preserveObjectStacking: true,
perPixelTargetFind: true, // To prevent the line having a selectable rectangle drawn around it and instead only have it selectable on direct click
});
fabricCanvas.on({
'object:selected': handleObjectSelected,
'object:moving': handleObjectMoving,
'object:modified': handleObjectModified,
});
addLineAnchors();
}
const drawLine = (points) => (
new Line(points)
)
button.addEventListener('click', () => {
drawingModeOn = !drawingModeOn;
setDrawingMode();
setButtonText();
});
initFabric();
setDrawingMode();
canvas {
border: 1px solid tomato;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.21/fabric.min.js"></script>
<canvas id="c"></canvas>
<button id="toggle-drawing-mode"></button>
答案 0 :(得分:1)
很难调试所有代码,但是在'mouse-up'
事件中,顶部和左侧位置被更改为旧行。在绘制newLine之前,我在**redrawPath**
函数中做了以下更改:
newLine.set({"top": newLine.pathOffset.y, "left": newLine.pathOffset.x});
repositionLineAnchors(newLine);
此外,在 transformedPoint
内,我删除了映射点的逻辑:
return points;
代替:
return points
.map(p => new fabric.Point(p.x - target.minX, p.y - target.minY))
.map(p => fabric.util.transformPoint(p, matrix));
这里是fiddle
更新:
您的逻辑几乎不错,您只需要设置
originX: "left"
和originY: "top"
而非'center'
target.width
-target.height
中减去target.strokeWidth
,map
和const halfStroke = target.get("strokeWidth") / 2;
return points.map(p => new fabric.Point(p.x - target.minX - target.width / 2 - halfStroke , p.y - target.minY - target.height / 2 - halfStroke)).map(p => fabric.util.transformPoint(p, matrix));
。mouse-move
事件上设置pathOffset,宽度和高度,例如: const dims = line._parseDimensions();
line.setWidth(dims.width);
line.setHeight(dims.height);
line.pathOffset.x = line.width/2 + line.left;
line.pathOffset.y = line.height/2 + line.top;
let canvas;
let line;
let lineAnchorStart;
let lineAnchorEnd;
let lineAnchorBend;
let activeItem;
let drawingModeOn = true;
let fabricCanvas = null;
const button = document.getElementById('toggle-drawing-mode');
const Line = fabric.util.createClass(fabric.Path, {
type: 'line',
initialize(element, options) {
options || (options = {});
this.callSuper('initialize', element, options);
// Set default options
this.set({
objectCaching: false,
hasControls: false,
// Comment out the below 2 lines
originX: 'left',
originY: 'top',
fill: 'transparent',
strokeWidth: 2,
stroke: 'black',
customProps: {
category: 'lines',
},
});
},
})
// Repositioning the line anchors after the line is moved or selected line is changed
const repositionLineAnchors = (line) => {
lineAnchorStart.set({
left: line.path[0][1],
top: line.path[0][2]
}).setCoords();
lineAnchorEnd.set({
left: line.path[1][3],
top: line.path[1][4]
}).setCoords();
// If the line is perfectly straight then we want to keep the bend anchor in the middle
// But if it has had bend applied to it then we let it stay where it was dragged
if ((line.path[1][1] === line.path[1][3]) && (line.path[1][2] === line.path[1][4])) {
const centerX = (line.path[0][1] + line.path[1][3]) / 2;
const centerY = (line.path[0][2] + line.path[1][4]) / 2;
lineAnchorBend.set({
left: centerX,
top: centerY
}).setCoords();
} else {
lineAnchorBend.set({
left: line.path[1][1],
top: line.path[1][2]
}).setCoords();
}
}
// If the line anchors themselves are moved the
const handleLineAnchorMove = (target) => {
switch (target.customProps.category) {
// Moving the line anchors
case 'line_anchor':
switch (target.customProps.type) {
case 'line_anchor_start':
activeItem.path[0][1] = target.left;
activeItem.path[0][2] = target.top;
activeItem.setCoords();
break;
case 'line_anchor_end':
// If the line is perfectly straight then we want to keep the quadratic value the same as the end point to avoid bending it
// But if it has had bend applied to it then the two can be treated separately
if ((activeItem.path[1][1] === activeItem.path[1][3]) && (activeItem.path[1][2] === activeItem.path[1][4])) {
activeItem.path[1][1] = target.left;
activeItem.path[1][2] = target.top;
}
activeItem.path[1][3] = target.left;
activeItem.path[1][4] = target.top;
activeItem.setCoords();
break;
case 'line_anchor_bend':
activeItem.path[1][1] = target.left;
activeItem.path[1][2] = target.top;
activeItem.setCoords();
break;
// no default
}
// no default
}
fabricCanvas.renderAll();
}
const transformedPoint = (target) => {
const points = [];
const path = target.path;
points.push(new fabric.Point(path[0][1], path[0][2]));
points.push(new fabric.Point(path[1][3], path[1][4]));
points.push(new fabric.Point(path[1][1], path[1][2]));
const matrix = target.calcTransformMatrix();
const halfStroke = target.get("strokeWidth") / 2;
return points
.map(p => new fabric.Point(p.x - target.minX - target.width / 2 - halfStroke, p.y - target.minY - target.height / 2 - halfStroke))
.map(p => fabric.util.transformPoint(p, matrix));
}
const redrawPath = (oldLine) => {
//oldLine.set({"originX": "left", "originY": "top"});
const transformedPoints = transformedPoint(oldLine);
const path = [
[],
[]
];
path[0][0] = 'M';
path[0][1] = transformedPoints[0].x;
path[0][2] = transformedPoints[0].y;
path[1][0] = 'Q';
path[1][1] = transformedPoints[2].x;
path[1][2] = transformedPoints[2].y;
path[1][3] = transformedPoints[1].x;
path[1][4] = transformedPoints[1].y;
const newLine = drawLine(path);
repositionLineAnchors(newLine);
fabricCanvas.remove(oldLine).add(newLine).setActiveObject(newLine).renderAll();
}
const addLine = () => {
let isDown;
let startPoint;
fabricCanvas.on('mouse:down', (options) => {
hideLineAnchors();
isDown = true;
startPoint = fabricCanvas.getPointer(options.e);
const path = [
[],
[]
];
path[0][0] = 'M';
path[0][1] = startPoint.x;
path[0][2] = startPoint.y;
path[1][0] = 'Q';
path[1][1] = startPoint.x;
path[1][2] = startPoint.y;
path[1][3] = startPoint.x;
path[1][4] = startPoint.y;
line = drawLine(path);
line.selectable = false; // This is needed to prevent newly added lines from being dragged if drawing a line right next to them
fabricCanvas.add(line).renderAll();
});
fabricCanvas.on('mouse:move', (options) => {
if (!isDown) return;
const pointer = fabricCanvas.getPointer(options.e);
line.path[1][1] = pointer.x;
line.path[1][2] = pointer.y;
line.path[1][3] = pointer.x;
line.path[1][4] = pointer.y;
const dims = line._parseDimensions();
line.setWidth(dims.width);
line.setHeight(dims.height);
line.pathOffset.x = line.width/2 + line.left;
line.pathOffset.y = line.height/2 + line.top;
lineAnchorEnd.set({
left: pointer.x,
top: pointer.y
});
line.setCoords();
fabricCanvas.renderAll();
});
fabricCanvas.on('mouse:up', (options) => {
isDown = false;
const endPoint = fabricCanvas.getPointer(options.e);
redrawPath(line);
disableDrawingMode();
});
}
const handleObjectSelected = (e) => {
let selectedItem = e.target;
switch (selectedItem.customProps.category) {
case 'line_anchor':
// If we select a line anchor we actually want the line to be the active object
selectedItem = activeItem;
disableDrawingMode();
break;
case 'lines':
repositionLineAnchors(selectedItem);
showLineAnchors();
fabricCanvas
.bringToFront(lineAnchorStart)
.bringToFront(lineAnchorEnd)
.bringToFront(lineAnchorBend)
.renderAll();
break;
}
activeItem = selectedItem;
}
const handleObjectMoving = (e) => {
const selectedItem = e.target;
// If not a group
if (selectedItem.customProps) {
switch (selectedItem.customProps.category) {
case 'line_anchor':
switch (selectedItem.customProps.type) {
case 'line_anchor_start':
case 'line_anchor_end':
lineAnchorBend.visible = false;
// no default
}
handleLineAnchorMove(selectedItem);
break;
case 'lines':
{
lineAnchorStart.visible === true && hideLineAnchors();
break;
}
// no default
}
}
}
const handleObjectModified = (e) => {
const selectedItem = e.target;
// If not a group
if (selectedItem.customProps) {
switch (selectedItem.customProps.category) {
case 'lines':
redrawPath(selectedItem);
showLineAnchors();
break;
case 'line_anchor':
redrawPath(activeItem);
showLineAnchors();
break;
// no default
}
}
}
const disableDrawingMode = () => {
drawingModeOn = false;
setButtonText();
fabricCanvas.selection = true;
fabricCanvas.forEachObject((object, i) => {
// This is to prevent the pitch background from being set to selectable (it is 0 in the object array)
if (i > 0) {
object.selectable = true;
}
});
fabricCanvas.defaultCursor = 'default';
fabricCanvas.hoverCursor = 'move';
// Remove event listeners
fabricCanvas
.off('mouse:down')
.off('mouse:move')
.off('mouse:up')
.off('mouse:out');
}
const enableDrawingMode = () => {
drawingModeOn = true;
setButtonText();
fabricCanvas.selection = false;
fabricCanvas.forEachObject((object) => {
object.selectable = false;
});
// Allow line anchors to be draggable while in drawing mode
lineAnchorStart.selectable = true;
lineAnchorEnd.selectable = true;
lineAnchorBend.selectable = true;
fabricCanvas.defaultCursor = 'crosshair';
fabricCanvas.hoverCursor = 'crosshair';
lineAnchorStart.hoverCursor = 'move';
lineAnchorEnd.hoverCursor = 'move';
lineAnchorBend.hoverCursor = 'move';
addLine();
}
const addLineAnchors = () => {
lineAnchorStart = createLineAnchor('line_anchor_start');
lineAnchorEnd = createLineAnchor('line_anchor_end');
lineAnchorBend = createLineAnchorBend('line_anchor_bend');
fabricCanvas.add(lineAnchorStart, lineAnchorEnd, lineAnchorBend);
}
const showLineAnchors = () => {
if (lineAnchorStart) {
lineAnchorStart.visible = true;
lineAnchorEnd.visible = true;
lineAnchorBend.visible = true;
}
}
const hideLineAnchors = () => {
if (lineAnchorStart) {
lineAnchorStart.visible = false;
lineAnchorEnd.visible = false;
lineAnchorBend.visible = false;
}
}
const createLineAnchor = anchorType => (
new fabric.Rect({
left: 0,
top: 0,
hasBorders: false,
hasControls: false,
originX: 'center',
originY: 'center',
height: 20,
width: 20,
strokeWidth: 2,
stroke: 'green',
fill: 'rgba(255, 255, 255, 0.1)',
visible: false,
excludeFromExport: true,
customProps: {
category: 'line_anchor',
type: anchorType,
},
})
)
const createLineAnchorBend = anchorType => (
new fabric.Circle({
left: 0,
top: 0,
hasBorders: false,
hasControls: false,
originX: 'center',
originY: 'center',
radius: 10,
strokeWidth: 2,
stroke: 'blue',
fill: 'rgba(63, 149, 220, 0.5)',
visible: false,
excludeFromExport: true,
customProps: {
category: 'line_anchor',
type: anchorType,
},
})
)
const setButtonText = () => {
if (drawingModeOn === true) {
button.textContent = 'Disable Drawing Mode';
} else {
button.textContent = 'Enable Drawing Mode';
}
}
const setDrawingMode = () => {
if (drawingModeOn === true) {
enableDrawingMode();
} else {
disableDrawingMode();
}
}
const initFabric = () => {
fabricCanvas = new fabric.Canvas('c', {
height: 1000,
width: 1000,
targetFindTolerance: 15,
selection: false,
preserveObjectStacking: true,
perPixelTargetFind: true, // To prevent the line having a selectable rectangle drawn around it and instead only have it selectable on direct click
});
fabricCanvas.on({
'object:selected': handleObjectSelected,
'object:moving': handleObjectMoving,
'object:modified': handleObjectModified,
});
addLineAnchors();
}
const drawLine = (points) => (
new Line(points)
)
button.addEventListener('click', () => {
drawingModeOn = !drawingModeOn;
setDrawingMode();
setButtonText();
});
initFabric();
setDrawingMode();
canvas {
border: 1px solid tomato;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.20/fabric.min.js"></script>
<canvas id="c"></canvas>
<button id="toggle-drawing-mode"></button>