Fabricjs - 仅通过边框选择
Fabricjs - selection only via border
我正在使用 Fabric.js
在 canvas 上绘制一些矩形。默认行为是在矩形内部单击选择它。如何更改行为,使其仅在单击矩形边框时才被选中?
在矩形内部而不是边框上单击应该不会执行任何操作。
您可以通过在 TradingView.com 图表上绘制一个矩形来查看此行为
在 fabric 中有一个选项,如果没有,我该如何实施它?
这是我的方法,当单击矩形时,我正在计算它被单击的位置并且
如果没有点击边框,我必须设置 canvas.discardActiveObject
,请参阅代码
的注释
var canvas = new fabric.Canvas('c', {
selection: false
});
var rect = new fabric.Rect({
left: 50,
top: 50,
width: 100,
height: 100,
strokeWidth: 10,
stroke: 'red',
selectable: false,
evented: true,
hasBorders: true,
lockMovementY: true,
lockMovementX: true
})
canvas.on("mouse:move", function(e) {
if (!e.target || e.target.type != 'rect') return;
// when selected event is fired get the click position.
var pointer = canvas.getPointer(e.e);
// calculate the click distance from object to be exact
var distanceX = pointer.x - rect.left;
var distanceY = pointer.y - rect.top;
// check if click distanceX/Y are less than 10 (strokeWidth) or greater than 90 ( rect width = 100)
if ((distanceX <= rect.strokeWidth || distanceX >= (rect.width - rect.strokeWidth)) || (distanceY <= rect.strokeWidth || distanceY >= (rect.height - rect.strokeWidth))) {
rect.set({
hoverCursor: 'move',
selectable: true,
lockMovementY: false,
lockMovementX: false
});
document.getElementById('result').innerHTML = 'on border';
} else {
canvas.discardActiveObject();
document.getElementById('result').innerHTML = 'not on border';
rect.set({
hoverCursor: 'default',
selectable: false,
lockMovementY: true,
lockMovementX: true
});
}
});
canvas.add(rect);
canvas.renderAll();
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.2/fabric.min.js"></script>
<div id="result" style="width: 100%; "></div>
<canvas id="c" width="600" height="200"></canvas>
<pre>
</pre>
ps:你也可以设置rect属性为selectable: false
并在if语句中调用canvas.setActiveObject(this);
使其选择。
Fabric.js 使用 Object.containsPoint()
来确定鼠标事件是否应该指向对象。此方法反过来通过 Object._getImageLines()
计算对象的边缘,并检查鼠标指针的投影穿过这些线的次数。
下面的解决方案根据每个角的坐标计算额外的内边,因此自动处理对象缩放和旋转。
const canvas = new fabric.Canvas('c', {
enableRetinaScaling: true
})
const rect = new fabric.Rect({
left: 0,
top: 0,
width: 100,
height: 100,
dragBorderWidth: 15, // this is the custom attribute we've introduced
})
function innerCornerPoint(start, end, offset) {
// vector length
const l = start.distanceFrom(end)
// unit vector
const uv = new fabric.Point((end.x - start.x) / l, (end.y - start.y) / l)
// point on the vector at a given offset but no further than side length
const p = start.add(uv.multiply(Math.min(offset, l)))
// rotate point
return fabric.util.rotatePoint(p, start, fabric.util.degreesToRadians(45))
}
rect._getInnerBorderLines = function(c) {
// the actual offset from outer corner is the length of a hypotenuse of a right triangle with border widths as 2 sides
const offset = Math.sqrt(2 * (this.dragBorderWidth ** 2))
// find 4 inner corners as offsets rotated 45 degrees CW
const newCoords = {
tl: innerCornerPoint(c.tl, c.tr, offset),
tr: innerCornerPoint(c.tr, c.br, offset),
br: innerCornerPoint(c.br, c.bl, offset),
bl: innerCornerPoint(c.bl, c.tl, offset),
}
return this._getImageLines(newCoords)
}
rect.containsPoint = function(point, lines, absolute, calculate) {
const coords = calculate ? this.calcCoords(absolute) : absolute ? this.aCoords : this.oCoords
lines = lines || this._getImageLines(coords)
const innerRectPoints = this._findCrossPoints(point, lines);
const innerBorderPoints = this._findCrossPoints(point, this._getInnerBorderLines(coords))
// calculate intersections
return innerRectPoints === 1 && innerBorderPoints !== 1
}
canvas.add(rect)
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.2/fabric.min.js"></script>
<canvas id="c" width="400" height="300"></canvas>
此方法会覆盖 FabricJS 中的 _checkTarget
方法,以拒绝距边界超过指定距离(由 clickableMargin
变量定义)的点击。
//sets the width of clickable area
var clickableMargin = 15;
var canvas = new fabric.Canvas("canvas");
canvas.add(new fabric.Rect({
width: 150,
height: 150,
left: 25,
top: 25,
fill: 'green',
strokeWidth: 0
}));
//overrides the _checkTarget method to add check if point is close to the border
fabric.Canvas.prototype._checkTarget = function(pointer, obj, globalPointer) {
if (obj &&
obj.visible &&
obj.evented &&
this.containsPoint(null, obj, pointer)){
if ((this.perPixelTargetFind || obj.perPixelTargetFind) && !obj.isEditing) {
var isTransparent = this.isTargetTransparent(obj, globalPointer.x, globalPointer.y);
if (!isTransparent) {
return true;
}
}
else {
var isInsideBorder = this.isInsideBorder(obj);
if(!isInsideBorder) {
return true;
}
}
}
}
fabric.Canvas.prototype.isInsideBorder = function(target) {
var pointerCoords = target.getLocalPointer();
if(pointerCoords.x > clickableMargin &&
pointerCoords.x < target.getScaledWidth() - clickableMargin &&
pointerCoords.y > clickableMargin &&
pointerCoords.y < target.getScaledHeight() - clickableMargin) {
return true;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.2/fabric.min.js"></script>
<canvas id="canvas" height="300" width="400"></canvas>
我正在使用 Fabric.js
在 canvas 上绘制一些矩形。默认行为是在矩形内部单击选择它。如何更改行为,使其仅在单击矩形边框时才被选中?
在矩形内部而不是边框上单击应该不会执行任何操作。
您可以通过在 TradingView.com 图表上绘制一个矩形来查看此行为
在 fabric 中有一个选项,如果没有,我该如何实施它?
这是我的方法,当单击矩形时,我正在计算它被单击的位置并且
如果没有点击边框,我必须设置 canvas.discardActiveObject
,请参阅代码
var canvas = new fabric.Canvas('c', {
selection: false
});
var rect = new fabric.Rect({
left: 50,
top: 50,
width: 100,
height: 100,
strokeWidth: 10,
stroke: 'red',
selectable: false,
evented: true,
hasBorders: true,
lockMovementY: true,
lockMovementX: true
})
canvas.on("mouse:move", function(e) {
if (!e.target || e.target.type != 'rect') return;
// when selected event is fired get the click position.
var pointer = canvas.getPointer(e.e);
// calculate the click distance from object to be exact
var distanceX = pointer.x - rect.left;
var distanceY = pointer.y - rect.top;
// check if click distanceX/Y are less than 10 (strokeWidth) or greater than 90 ( rect width = 100)
if ((distanceX <= rect.strokeWidth || distanceX >= (rect.width - rect.strokeWidth)) || (distanceY <= rect.strokeWidth || distanceY >= (rect.height - rect.strokeWidth))) {
rect.set({
hoverCursor: 'move',
selectable: true,
lockMovementY: false,
lockMovementX: false
});
document.getElementById('result').innerHTML = 'on border';
} else {
canvas.discardActiveObject();
document.getElementById('result').innerHTML = 'not on border';
rect.set({
hoverCursor: 'default',
selectable: false,
lockMovementY: true,
lockMovementX: true
});
}
});
canvas.add(rect);
canvas.renderAll();
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.2/fabric.min.js"></script>
<div id="result" style="width: 100%; "></div>
<canvas id="c" width="600" height="200"></canvas>
<pre>
</pre>
ps:你也可以设置rect属性为selectable: false
并在if语句中调用canvas.setActiveObject(this);
使其选择。
Fabric.js 使用 Object.containsPoint()
来确定鼠标事件是否应该指向对象。此方法反过来通过 Object._getImageLines()
计算对象的边缘,并检查鼠标指针的投影穿过这些线的次数。
下面的解决方案根据每个角的坐标计算额外的内边,因此自动处理对象缩放和旋转。
const canvas = new fabric.Canvas('c', {
enableRetinaScaling: true
})
const rect = new fabric.Rect({
left: 0,
top: 0,
width: 100,
height: 100,
dragBorderWidth: 15, // this is the custom attribute we've introduced
})
function innerCornerPoint(start, end, offset) {
// vector length
const l = start.distanceFrom(end)
// unit vector
const uv = new fabric.Point((end.x - start.x) / l, (end.y - start.y) / l)
// point on the vector at a given offset but no further than side length
const p = start.add(uv.multiply(Math.min(offset, l)))
// rotate point
return fabric.util.rotatePoint(p, start, fabric.util.degreesToRadians(45))
}
rect._getInnerBorderLines = function(c) {
// the actual offset from outer corner is the length of a hypotenuse of a right triangle with border widths as 2 sides
const offset = Math.sqrt(2 * (this.dragBorderWidth ** 2))
// find 4 inner corners as offsets rotated 45 degrees CW
const newCoords = {
tl: innerCornerPoint(c.tl, c.tr, offset),
tr: innerCornerPoint(c.tr, c.br, offset),
br: innerCornerPoint(c.br, c.bl, offset),
bl: innerCornerPoint(c.bl, c.tl, offset),
}
return this._getImageLines(newCoords)
}
rect.containsPoint = function(point, lines, absolute, calculate) {
const coords = calculate ? this.calcCoords(absolute) : absolute ? this.aCoords : this.oCoords
lines = lines || this._getImageLines(coords)
const innerRectPoints = this._findCrossPoints(point, lines);
const innerBorderPoints = this._findCrossPoints(point, this._getInnerBorderLines(coords))
// calculate intersections
return innerRectPoints === 1 && innerBorderPoints !== 1
}
canvas.add(rect)
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.2/fabric.min.js"></script>
<canvas id="c" width="400" height="300"></canvas>
此方法会覆盖 FabricJS 中的 _checkTarget
方法,以拒绝距边界超过指定距离(由 clickableMargin
变量定义)的点击。
//sets the width of clickable area
var clickableMargin = 15;
var canvas = new fabric.Canvas("canvas");
canvas.add(new fabric.Rect({
width: 150,
height: 150,
left: 25,
top: 25,
fill: 'green',
strokeWidth: 0
}));
//overrides the _checkTarget method to add check if point is close to the border
fabric.Canvas.prototype._checkTarget = function(pointer, obj, globalPointer) {
if (obj &&
obj.visible &&
obj.evented &&
this.containsPoint(null, obj, pointer)){
if ((this.perPixelTargetFind || obj.perPixelTargetFind) && !obj.isEditing) {
var isTransparent = this.isTargetTransparent(obj, globalPointer.x, globalPointer.y);
if (!isTransparent) {
return true;
}
}
else {
var isInsideBorder = this.isInsideBorder(obj);
if(!isInsideBorder) {
return true;
}
}
}
}
fabric.Canvas.prototype.isInsideBorder = function(target) {
var pointerCoords = target.getLocalPointer();
if(pointerCoords.x > clickableMargin &&
pointerCoords.x < target.getScaledWidth() - clickableMargin &&
pointerCoords.y > clickableMargin &&
pointerCoords.y < target.getScaledHeight() - clickableMargin) {
return true;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.2/fabric.min.js"></script>
<canvas id="canvas" height="300" width="400"></canvas>