Any suggestion on select multi objects on canvas mouse click? not all object, I want to select objects that overlay on the point.
For my knowledge, the target on mouse event is always the top most object only.
I had tried to bind event on object, but it wont fired for those at the back side.
I had tried select based on item size and height, but it is not working perfectly after rotate.
var canvas = this.__canvas = new fabric.Canvas('c', {
enableRetinaScaling: false
});
function LoopOnObjects(e) {
var mouse = canvas.getPointer(e.e, false);
var x = Math.ceil(mouse.x);
var y = Math.ceil(mouse.y);
var count = 0;
canvas.getObjects().forEach(function(object, index){
if(CheckObjectWithin(object, x, y)) {
count++;
}
});
alert("ya, there is " + count + " objects touch on click");
}
function CheckObjectWithin(object, x, y) {
var objectBoundRect = object.getBoundingRect(true);
var widthRange = objectBoundRect.width;
var heightRange = objectBoundRect.height;
if (x > objectBoundRect.left && x < objectBoundRect.left + widthRange) {
if (y > objectBoundRect.top && y < objectBoundRect.top + heightRange) {
return true;
}
}
return false;
}
function GetElement(e) {
LoopOnObjects(e);
}
canvas.on("mouse:up", GetElement);
canvas.add(new fabric.Rect({
width: 100, height: 100, left: 100, top: 20, angle: -10,
fill: 'rgba(0,200,0,0.5)'
}));
canvas.add(new fabric.Rect({
width: 50, height: 100, left: 220, top: 80, angle: 45,
stroke: '#eee', strokeWidth: 10,
fill: 'rgba(0,0,200,0.5)'
}));
canvas.add(new fabric.Circle({
radius: 50, left: 220, top: 175, fill: '#aac'
}));
template for testing
Actually, there is already a library method for that: fabric.Object.prototype.containsPoint(). It works with rotate, but keep in mind that the point is checked against the bounding box, not the visible shape (e.g. circle shape still has a rectangular bounding box).
var canvas = this.__canvas = new fabric.Canvas('c');
function loopOnObjects(e) {
var mouse = canvas.getPointer(e.e, false);
var point = new fabric.Point(mouse.x, mouse.y)
var count = 0;
canvas.getObjects().forEach(function(object, index){
if (object.containsPoint(point)) {
count++;
}
});
}
function getElement(e) {
loopOnObjects(e);
}
canvas.on("mouse:down", getElement);
canvas.add(new fabric.Rect({
width: 100, height: 100, left: 100, top: 20, angle: -10,
fill: 'rgba(0,200,0,0.5)'
}));
canvas.add(new fabric.Rect({
width: 50, height: 100, left: 220, top: 80, angle: 45,
stroke: '#eee', strokeWidth: 10,
fill: 'rgba(0,0,200,0.5)'
}));
canvas.add(new fabric.Circle({
radius: 50, left: 220, top: 175, fill: '#aac'
}));
#c {
border: 1px black solid;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.22/fabric.min.js"></script>
<canvas id="c" width="500" height="300"></canvas>
Related
Version
4.2.0
Test Case
https://jsfiddle.net/dellvolk/c8zas7th/12/
let optObj = {
width: 300,
height: 300,
backgroundColor: "rgba(255,255,0,0.5)",
}
let canvas = new fabric.Canvas('c', optObj);
const stroke = {
stroke: 'red',
strokeWidth: 10,
}
const rect1 = new fabric.Rect({
left: 30,
top: 30,
...stroke,
width: 100,
height: 100,
fill: 'blue',
padding: -stroke.strokeWidth,
borderColor: '#000',
})
const rect2 = new fabric.Rect({
left: 130,
top: 130,
...stroke,
width: 100,
height: 100,
fill: 'green',
padding: -stroke.strokeWidth,
borderColor: '#000',
})
fabric.Object.prototype.padding = -10;
canvas.add(rect1);
canvas.add(rect2);
Expected Behavior
I need that when objects form group padding remained. I don't need to set a padding for the group. Only for elements in this group. That the stroke of the group remained as it is, and the black stroke of the elements of the group was like the padding of the elements themselves
Actual Behavior
Padding in objects is -10, but when they form group, the padding changes to 0
The method that handles the border size calculation for objects in a group is drawBordersInGroup (see http://fabricjs.com/docs/fabric.Object.html#drawBordersInGroup).
The following should work as a drop-in replacement for this method in order to factor padding into the size calculation:
https://jsfiddle.net/melchiar/bw384o2n/
//drop in replacement for drawBordersInGroup method
fabric.Object.prototype.drawBordersInGroup = function(ctx, options, styleOverride) {
styleOverride = styleOverride || {};
var bbox = fabric.util.sizeAfterTransform(this.width, this.height, options),
strokeWidth = this.strokeWidth,
strokeUniform = this.strokeUniform,
//borderScaleFactor = this.borderScaleFactor,
//adds object padding to border scale calculation
borderScaleFactor = this.borderScaleFactor + this.padding * 2,
width =
bbox.x + strokeWidth * (strokeUniform ? this.canvas.getZoom() : options.scaleX) + borderScaleFactor,
height =
bbox.y + strokeWidth * (strokeUniform ? this.canvas.getZoom() : options.scaleY) + borderScaleFactor;
ctx.save();
this._setLineDash(ctx, styleOverride.borderDashArray || this.borderDashArray, null);
ctx.strokeStyle = styleOverride.borderColor || this.borderColor;
ctx.strokeRect(
-width / 2,
-height / 2,
width,
height
);
ctx.restore();
return this;
};
I have an object e.g circle and I want to add more circles on border of the parent circle that must be center aligned.
On the attached gif image you can see my cursor - it only allows me to create a child circle on border of the parent.
Till now I was only able to create the parent circle.
var circle = new fabric.Circle({
strokeWidth: 1,
stroke: "#222",
noScaleCache: false,
strokeUniform: true,
scaleX: 1,
scaleY: 1,
left: canvas.getWidth() / 2,
top: canvas.getHeight() / 2,
radius: fabric.util.parseUnit(circleSize + 'in')
});
Basically you need to calculate Euclidean distance between the circle that you want to draw and the main circle, and if the distance is near to the border of the main circle you just draw a circle at the mouse position where you clicked.
The distance is calculated using Math.hypot function.
And here is the code.
var canvas = new fabric.Canvas('c', {
selection: false
});
var circleRadius = 100;
var c = new fabric.Circle({
strokeWidth: 3,
stroke: "#222",
fill: "#55ff55",
noScaleCache: false,
strokeUniform: true,
selectable: false,
scaleX: 1,
scaleY: 1,
left: canvas.getWidth() / 2 - circleRadius,
top: canvas.getHeight() / 2 - circleRadius,
radius: circleRadius
});
canvas.add(c);
var circleAngle = Math.atan2(c.getCenterPoint().x,c.getCenterPoint().y);
var circle = new fabric.Circle({
strokeWidth: 3,
stroke: "#222",
radius: 30,
selectable: true,
originX: 'center',
originY: 'center',
fill: "#ff5555ff",
left: Math.sin(circleAngle) * circleRadius + canvas.getWidth() / 2,
top: Math.cos(circleAngle) * circleRadius + canvas.getHeight() / 2
});
canvas.add(circle);
var isDown, origX, origY;
canvas.on('mouse:down', function(o) {
var pointer = canvas.getPointer(o.e);
origX = pointer.x;
origY = pointer.y;
if (isDown) {
circle.set({
strokeWidth: 3,
stroke: "#222",
radius: 30,
left: pointer.x,
top: pointer.y,
fill: "#ff5555ff"
});
canvas.add(circle);
circle = new fabric.Circle({
strokeWidth: 3,
stroke: "#222",
radius: 1,
selectable: true,
originX: 'center',
originY: 'center'
});
canvas.add(circle);
}
});
canvas.on('mouse:move', function(o) {
var pointer = canvas.getPointer(o.e);
var x = pointer.x - c.getCenterPoint().x;
var y = pointer.y - c.getCenterPoint().y;
circle.set({
strokeWidth: 3,
stroke: "#222",
radius: 30,
left: Math.sin(Math.atan2(x,y))*c.radius + canvas.getWidth() / 2,
top: Math.cos(Math.atan2(x,y))*c.radius + canvas.getHeight() / 2,
fill: "#ff5555ff"
});
if (nearToBorder(pointer, c.getCenterPoint(), c.radius)) {
isDown = true;
} else {
isDown = false;
}
canvas.renderAll();
});
canvas.on('mouse:up', function(o) {
isDown = false;
});
function nearToBorder(pointer, circlePoint, r) {
var d = Math.hypot(pointer.x - (circlePoint.x), pointer.y - (circlePoint.y));
return d >= r - 10 && d <= r + 10;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.0.0-rc.1/fabric.min.js"></script>
<canvas id="c" width="500" height="500" style="border:1px solid #ccc"></canvas>
In the below Image I have three boxes in the canvas and there are three buttons at the bottom of the image. Whenever I click a button, the corresponding object in the canvas gets selected(i,e, when I click the green button, green rectangle in the canvas, gets selected).
My requirement is to highlight only the selected portion and other portion of the canvas should be grayed out. (Ex: If I click the green button green rectangle should be selected and other portion should be overlayed with a gray background).
Js Fiddle Link: https://jsfiddle.net/rnvs2hdk/1/
var canvas = new fabric.Canvas('c');
canvas.backgroundColor = 'yellow';
var li= []
canvas.renderAll();
fabric.Image.fromURL('http://fabricjs.com/assets/pug_small.jpg', function(myImg) {
var img1 = myImg.set({ left: 0, top: 0 ,width:400,height:500});
canvas.add(img1);
var green = new fabric.Rect({
left: 50,
top: 50,
width: 50,
height: 50,
fill: 'rgba(255,255,255,1)',
stroke: 'rgba(34,177,76,1)',
strokeWidth: 5,
name:"green"
});
var yellow = new fabric.Rect({
left: 150,
top: 50,
width: 50,
height: 50,
fill: 'rgba(255,255,255,1)',
stroke: 'rgba(255,255,0,1)',
strokeWidth: 5,
name:"yellow"
});
var red = new fabric.Rect({
left: 250,
top: 50,
width: 50,
height: 50,
fill: 'rgba(255,255,255,1)',
stroke: 'rgba(255,0,0,1)',
strokeWidth: 5,
name:"red"
});
canvas.add(green, yellow,red);
li.push(green);
li.push(yellow);
li.push(red);
li.some(v=>{
var btn = document.createElement("BUTTON"); // Create a <button> elem
btn.innerHTML = v.name;
btn.addEventListener('click',function(e){
var name = e.target
if(name.innerText == "green"){
canvas.setActiveObject(li[0]);
}
if(name.innerText == "yellow"){
canvas.setActiveObject(li[1]);
}
if(name.innerText == "red"){
canvas.setActiveObject(li[2]);
}
});// Insert text
document.body.appendChild(btn);
});
console.log(li);
});
Expected Result:(example)
Here's my solution. Using the after:render event, you can perform canvas draw actions over each frame after it is rendered. This approach has the benefit of avoiding having to create and destroy fabric objects as needed which is an expensive operation.
Be sure to call the .setCoords() method during actions like scaling and moving so that objects will update their position information while performing these actions.
var canvas = new fabric.Canvas('c');
canvas.backgroundColor = 'yellow';
var li = [];
canvas.renderAll();
fabric.Image.fromURL('http://fabricjs.com/assets/pug_small.jpg', function(myImg) {
var img1 = myImg.set({
left: 0,
top: 0,
width: 400,
height: 500
});
canvas.add(img1);
var green = new fabric.Rect({
left: 50,
top: 50,
width: 50,
height: 50,
fill: 'rgba(255,255,255,1)',
stroke: 'rgba(34,177,76,1)',
strokeWidth: 5,
name: "green",
hasRotatingPoint: false
});
var yellow = new fabric.Rect({
left: 150,
top: 50,
width: 50,
height: 50,
fill: 'rgba(255,255,255,1)',
stroke: 'rgba(255,255,0,1)',
strokeWidth: 5,
name: "yellow",
hasRotatingPoint: false
});
var red = new fabric.Rect({
left: 250,
top: 50,
width: 50,
height: 50,
fill: 'rgba(255,255,255,1)',
stroke: 'rgba(255,0,0,1)',
strokeWidth: 5,
name: "red",
hasRotatingPoint: false
});
canvas.add(green, yellow, red);
li.push(green);
li.push(yellow);
li.push(red);
li.some(v => {
var btn = document.createElement("BUTTON"); // Create a <button> elem
btn.innerHTML = v.name;
btn.addEventListener('click', function(e) {
var name = e.target
if (name.innerText == "green") {
canvas.setActiveObject(li[0]);
}
if (name.innerText == "yellow") {
canvas.setActiveObject(li[1]);
}
if (name.innerText == "red") {
canvas.setActiveObject(li[2]);
}
}); // Insert text
document.body.appendChild(btn);
});
console.log(li);
});
canvas.on({
'object:moving': function(e) {
//makes objects update their coordinates while being moved
e.target.setCoords();
},
'object:scaling': function(e) {
//makes objects update their coordinates while being scaled
e.target.setCoords();
}
});
//the after:render event allows you to perform a draw function on each frame after it is rendered
canvas.on('after:render', function() {
var ctx = canvas.contextContainer,
obj = canvas.getActiveObject();
if (obj) {
//set the fill color of the overlay
ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
var bound = obj.getBoundingRect();
ctx.beginPath();
//draw rectangle to the left of the selection
ctx.rect(0, 0, bound.left, canvas.height);
//draw rectangle to the right of the selection
ctx.rect(bound.left + bound.width, 0, canvas.width - bound.left - bound.width, canvas.height);
//draw rectangle above the selection
ctx.rect(bound.left, 0, bound.width, bound.top);
//draw rectangle below the selection
ctx.rect(bound.left, bound.top + bound.height, bound.width, canvas.height - bound.top - bound.height)
ctx.fill();
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.5.0/fabric.min.js"></script>
<canvas id="c" width="400" height="400"></canvas>
<div id="bt"></div>
I have Accomplished this creating 4 more rectangles around the actual rectangle. I have grayed out the outer rectangles so it gives the overlay effect. Also, I will delete all the rectangles before creating a new one.
let blankColor = "rgba(0,0,0,0)";
let grayOut = "rgba(0,0,0,0.4)";
let rect = createRect(x, y, width, height, 1, blankColor);
let rect1 = createRect(0, 0, getViewPortDimensions()[0], y, 0, grayOut);
let rect2 = createRect(0, y + height, getViewPortDimensions()[0], getViewPortDimensions()[1] - (y + height), 0, grayOut);
let rect3 = createRect(0, y, x, height, 0, grayOut);
let rect4 = createRect(x + width, y, getViewPortDimensions()[0] - (x + width), height, 0, grayOut);
state.canvas.add(rect, rect1, rect2, rect3, rect4);
Deleting the already drawn rectangle:
state.canvas.forEachObject((o, index) => {
if (index != 0) {
state.canvas.remove(o);
}
})
Is there a simple way of clipping an image with rounded corner and inner stroke?
https://jsfiddle.net/4tursk3y/
$('#clip').click(function() {
canvas._objects[1].set({
'clipTo': function(ctx) {
var rect = new fabric.Rect({
left: 0,
top: 0,
rx: 20 / this.scaleX,
ry: 20 / this.scaleY,
width: this.width,
height: this.height,
fill: '#000000'
});
rect._render(ctx, false);
}
});
canvas.renderAll();
});
You can use a rectangle with a pattern fill to get rounded image corners without clipping the stroke.
var rect = new fabric.Rect({
left: 10,
top: 10,
width: 140,
height: 215,
stroke: 'red',
strokeWidth: 3,
rx:10,
ry:10
});
canvas.add(rect);
fabric.util.loadImage('http://fabricjs.com/assets/pug.jpg', function (img) {
rect.setPatternFill({
source: img,
repeat: 'no-repeat',
patternTransform: [0.2, 0, 0, 0.2, 0, 0]
});
canvas.renderAll();
});
https://jsfiddle.net/melchiar/78nt10ua/
for newer fabric versions use this
const roundedCorners = (fabricObject, cornerRadius) => new fabric.Rect({
width: fabricObject.width,
height: fabricObject.height,
rx: cornerRadius / fabricObject.scaleX,
ry: cornerRadius / fabricObject.scaleY,
left: -fabricObject.width / 2,
top: -fabricObject.height / 2
})
image.set("clipPath", roundedCorners(image, 20))
I do have a group with multiple objects. If the group is not rotated. Everything looks fine and controls are as expected.
However, when I take exactly the same group and set the rotation angle programmatically before rendering, my controls coordinates equal the bounding rect, but not the group coordinates anymore.
I have created a simplied version of the problem here: http://jsfiddle.net/schacki/avd6sjps/2/
fabric.Object.prototype.originX = "center";
fabric.Object.prototype.originY = "center";
// init canvas
var canvas = window._canvas = new fabric.Canvas('c');
var angle = 20;
// Group 1: no rotation
var left1 = new fabric.Rect({
width: 50,
height: 50,
fill: 'red',
left:75,
top: 75,
originX: "center",
originY: "center"
});
var middle1 = new fabric.Rect({
radius: 50,
fill: 'green',
width: 300,
height: 100,
left: 200,
top: 100,
originX: "center",
originY: "center"
});
var right1 = new fabric.Rect({
width: 50,
height: 50,
fill: 'blue',
left:325,
top: 125,
originX: "center",
originY: "center"
});
var group1 = new fabric.Group([middle1, left1, right1],{angle: 45});
// Group 1: no rotation
var left2 = new fabric.Rect({
angle: 45,
width: 50,
height: 50,
fill: 'red',
left:75+54,
top: 275-80,
originX: "center",
originY: "center"
});
var middle2 = new fabric.Rect({
angle: 45,
radius: 50,
fill: 'green',
width: 300,
height: 100,
left: 200,
top: 300,
originX: "center",
originY: "center"
});
var right2 = new fabric.Rect({
angle: 45,
width: 50,
height: 50,
fill: 'blue',
left:325-54,
top: 325+80,
originX: "center",
originY: "center"
});
var group2 = new fabric.Group([middle2, left2, right2]);
canvas.add(group1);
canvas.add(group2);
canvas.setActiveObject(group1);
canvas.setActiveObject(group2);
Basically I would like to have the controls in group 2 to look like the controls of group 1.
Thanks a lot
There is no built in way to do this.
As you see the difference is setting up the angle on the objects or on groups.
What you can do is write a small procedure that cycle trough the group._objects and decide which is the best angle for you. You subtract that angle to every object and you add on group.
Be aware that is not easy choosing a good one. You have to take in account which object is the biggest or the one that takes more space and use as "master" angle if you want compact controls.