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>
Related
I have following example stage.
const rectangle = new Konva.Rect({
width: 300,
height: 100,
x: stage.width() / 2,
y: stage.height() / 2,
fill: 'blue',
rotation: 140
});
layer.add(rectangle);
layer.draw();
const tooltip = new Konva.Label({
x: rectangle.x() + (rectangle.width() / 2),
y: rectangle.y() + (rectangle.height() / 2),
opacity: 0.75
});
tooltip.add(
new Konva.Tag({
fill: 'green',
pointerDirection: 'down',
pointerWidth: 6,
pointerHeight: 4,
lineJoin: 'round'
})
);
tooltip.add(new Konva.Text({
text: 'Hello world',
fontSize: 12,
padding: 5,
fill: 'white',
}));
layer.add(tooltip);
layer.draw();
When the rectangle has no rotation I am able to place the tooltip correctly in the centre of the node. When the rectangle has some rotation the tooltip is not centred any more.
Without changing the attributes of the rectangle or rotating the tooltip, how can I place the tooltip in the centre of the rectangle node?
JSBIN example:
https://jsbin.com/wopoxuligi/edit?html,js,output
First, you need to update your declaration for Konva in the html script tag as you are referencing an old version.
<script src="https://unpkg.com/konva#7.0.3/konva.min.js"></script>
Because this shape is a rect you can call rectangle.getClientRect() to get the position on the stage, then use simple center-math to place your label. Slip this code in at the end of your current code where you do the last layer.draw();
const cliRect = rectangle.getClientRect();
let pos = {
x: cliRect.x + (cliRect.width )/2,
y: cliRect.y + (cliRect.height )/2
};
tooltip.x(pos.x)
tooltip.y(pos.y)
layer.draw();
This will leave the pointer of the label pointing at the centre of the rotated rectangle, with the label being non-rotated. Working snippet below - run it full screen.
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
draggable: true
});
const layer = new Konva.Layer();
stage.add(layer);
const rectangle = new Konva.Rect({
width: 300,
height: 100,
x: stage.width() / 2,
y: stage.height() / 2,
fill: 'blue',
rotation: 140
});
layer.add(rectangle);
layer.draw();
const tooltip = new Konva.Label({
x: rectangle.x() + (rectangle.width() / 2),
y: rectangle.y() + (rectangle.height() / 2),
opacity: 0.75
});
tooltip.add(
new Konva.Tag({
fill: 'green',
pointerDirection: 'down',
pointerWidth: 6,
pointerHeight: 4,
lineJoin: 'round'
})
);
tooltip.add(new Konva.Text({
text: 'Hello world',
fontSize: 12,
padding: 5,
fill: 'white',
}));
layer.add(tooltip);
const cliRect = rectangle.getClientRect();
// Extra red rect to illustrate client rect. Not needed in solution
const rectangle1 = new Konva.Rect({
width: cliRect.width,
height: cliRect.height,
x: cliRect.x,
y: cliRect.y,
stroke: 'red'
});
layer.add(rectangle1);
let pos = {
x: cliRect.x + (cliRect.width )/2,
y: cliRect.y + (cliRect.height )/2
};
// Set tooltip position taking account of stage draggind
tooltip.x(pos.x - stage.x())
tooltip.y(pos.y - stage.y())
console.log(pos)
layer.draw();
<script src="https://unpkg.com/konva#7.0.3/konva.min.js"></script>
<div id="container"></div>
EDIT: In a comment the OP pointed out that the real use case requires the stage to be draggable before the label is added and that after the drag the calc of the label position fails. This is because getClientRect() gives the position in the client container without adjustment for the stage movement. To make the code work with a dragged stage, change the two lines that set tooltip.x & y to adjust by the stage.x & y, as follows:
tooltip.x(pos.x - stage.x())
tooltip.y(pos.y - stage.y())
I added offset to rectangle, to rotate it by its center point and managed to have the tooltip centered.
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight
});
const layer = new Konva.Layer();
stage.add(layer);
var group1 = new Konva.Group({
draggable: true
});
const rectWidth = 300;
const rectHeight = 100;
const rectangle = new Konva.Rect({
width: rectWidth,
height: rectHeight,
x: stage.width() / 2,
y: stage.height() / 2,
fill: 'blue',
rotation: 140,
offset: {
x: rectWidth / 2,
y: rectHeight / 2,
},
});
const tooltip = new Konva.Label({
x: rectangle.x() + (rectangle.width() / 2),
y: rectangle.y() + (rectangle.height() / 2),
offset: {
x: rectangle.width() / 2,
y: rectangle.height() / 2,
},
opacity: 0.75
});
tooltip.add(
new Konva.Tag({
fill: 'green',
pointerDirection: 'down',
pointerWidth: 6,
pointerHeight: 4,
lineJoin: 'round'
})
);
tooltip.add(new Konva.Text({
text: 'Hello world',
fontSize: 12,
padding: 5,
fill: 'white',
}));
layer.add(rectangle);
layer.add(tooltip);
layer.draw();
<script src="https://unpkg.com/konva#^2/konva.min.js"></script>
<div id="container"></div>
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'd like to use a polyline to connect two rectangles and stay connected to the same points on the respective rectangles as the rectangles move (specifically, for two rectangles on a page, I'd like the polyline to connect the bottom middle point of one rectangle to the top middle part of another rectangle. The reason I'm using a PolyLine is because I will eventually be adding in elbows down the road as well). I'm having issues with the polyline coordinates updating in response to the moving rectangles though.
This demonstrates some of the issues I am hitting:
var canvas = new fabric.Canvas('c');
rect = null;
line = null;
function addLine(x1, y1, x2, y2) {
var coords = [{x: x1, y: y1}, {x: x2, y: y2}];
this.line = new fabric.Polyline(coords, {
stroke: 'green',
strokeWidth: 5,
fill: 'rgba(0,0,0,0)',
selectable: true,
evented: false
});
this.canvas.add(this.line);
}
function addRect(left, top, width, height, line1, line2, line3, line4) {
this.rect = new fabric.Rect({
left: left,
top: top,
width: width,
height: height,
fill: '#9f9',
originX: 'left',
originY: 'top',
centeredRotation: true
});
this.rect.line1 = line1;
this.rect.line2 = line2;
this.rect.line3 = line3;
this.rect.line4 = line4;
this.canvas.add(this.rect);
}
var r1_left = 10;
var r1_top = 20;
var r1_width = 125;
var r1_height = 150;
var r2_left = 350;
var r2_top = 300;
var r2_width = 125;
var r2_height = 150;
addLine(r1_left + r1_width/2, r1_top + r1_height, r2_left + r2_width/2, r2_top);
addRect(r1_left, r1_top, r1_width, r1_height, null, null, this.line, null);
addRect(r2_left, r2_top, r2_width, r2_height, this.line, null, null, null);
this.canvas.renderAll();
this.canvas.on('object:moving', function(e) {
var p = e.target;
if (p.line1) {
let x_2_new = p.left + p.width/2;
let y_2_new = p.top;
p.line1.set('points', [p.line1.points[0], {'x': x_2_new, 'y': y_2_new}]);
p.line1.set('height', y_2_new - p.line1.points[0]['y']);
p.line1.set('width', x_2_new - p.line1.points[0]['x']);
p.set('oCoords', p.line1.calcCoords());
} else if (p.line2) {
p.line2.set({'points': [{'x': p.left + p.width, 'y': p.top + p.height/2}, p.line2.points[1]]});
} else if (p.line3) {
p.line3.set({'points': [{'x': p.left + p.width/2, 'y': p.top + p.height}, p.line3.points[1]]});
} else if (p.line4) {
p.line4.set({'points': [p.line4.points[0], {'x': p.left, 'y': p.top + p.height/2}]});
}
});
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.js"></script>
</head>
<body>
<canvas id="c" width="700" height="700" style="border:1px solid #ccc"></canvas>
<script>
</script>
</body>
</html>
For the upper rectangle, I have tried simply setting the x and y coordinates as the object moves. With this, I encounter the error that x and y seem to be bound by the oCoords and aCoords of the line.
For the lower rectangle, I have tried setting the coordinates directly. With this, the entire line seems to shift around the page.
Any advice about what I could change here would be great. Thanks!
Here is the code, what you need, jsfiddle
(function() {
var canvas = this.__canvas = new fabric.Canvas('c', { selection: false });
fabric.Object.prototype.originX = fabric.Object.prototype.originY = 'center';
function makeCircle(left, top, line1, line2) {
var c = new fabric.Rect({
top: top,
left: left,
width: 30,
height: 30,
selection: false,
fill: '#ccc'
});
c.hasControls = c.hasBorders = false;
c.line1 = line1;
c.line2 = line2;
return c;
}
function makeLine(coords) {
return new fabric.Line(coords, {
fill: 'red',
stroke: 'red',
strokeWidth: 5,
selectable: false,
evented: false,
});
}
var line = makeLine([ 250, 125, 250, 375 ]),
line2 = makeLine([ 250, 375, 250, 350 ]);
canvas.add(line);
canvas.add(
makeCircle(line.get('x1'), line.get('y1'), null, line),
makeCircle(line.get('x2'), line.get('y2'), line, line2),
);
canvas.on('object:moving', function(e) {
var p = e.target;
p.line1 && p.line1.set({ 'x2': p.left, 'y2': p.top });
p.line2 && p.line2.set({ 'x1': p.left, 'y1': p.top });
canvas.renderAll();
});
})();
<div>
<canvas id="c" width="700" height="575" style="border:1px solid #999"></canvas>
</div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/fabric.js/2.7.0/fabric.min.js"></script>
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>
I am trying to edit another example that was posted on here but something doesn't seem right. I have a shape and then a couple of shapes that are grouped together with an arrow that is pointing in between the two shapes. As one of the shapes is dragged the arrow position should move as well.
The Problem
The problem is that the shape seems to be on a different coordinate system where its 0,0 point is in the upper left corner, whereas the groups coordinate system where its 0,0 point is right in the middle of the screen. What am I doing wrong?
Code
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.rawgit.com/konvajs/konva/0.13.0/konva.min.js"></script>
<meta charset="utf-8">
<title>Konva Circle Demo</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #F0F0F0;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
var width = window.innerWidth;
var height = window.innerHeight;
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height
});
var layer = new Konva.Layer();
var group = new Konva.Group({
x:120,
y:120,
draggable: true,
});
var circle = new Konva.Circle({
x: stage.getWidth() / 2,
y: stage.getHeight() / 2,
radius: 40,
fill: 'green',
stroke: 'black',
strokeWidth: 2,
});
var circleA = new Konva.Circle({
x: stage.getWidth() / 5,
y: stage.getHeight() / 5,
radius: 30,
fill: 'red',
stroke: 'black',
strokeWidth: 2,
draggable: true
});
var arrow = new Konva.Arrow({
points: [circle.getX(), circle.getY(), circleA.getX(), circleA.getY()],
pointerLength: 10,
pointerWidth: 10,
fill: 'black',
stroke: 'black',
strokeWidth: 4
});
var star = new Konva.Star({
x: stage.getWidth() / 2,
y: stage.getHeight() / 2,
numPoints: 5,
innerRadius: 30,
outerRadius: 50,
fill: '#89b717',
opacity: 0.8,
scale: {
x : 1.4,
y : 1.4
},
rotation: Math.random() * 180,
shadowColor: 'black',
shadowBlur: 10,
shadowOffset: {
x : 5,
y : 5
},
shadowOpacity: 0.6,
});
//layer.add(star);
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height
});
function adjustPoint(e){
var p=[circle.getX(), circle.getY(), circleA.getX(), circleA.getY()];
arrow.setPoints(p);
layer.draw();
console.log(group.getX(),group.getY());
console.log(circleA.getX(),circleA.getY());
}
//circle.on('dragmove', adjustPoint);
group.on('dragmove', adjustPoint);
circleA.on('dragmove', adjustPoint);
group.add(star,circle);
//group.add(circle);
layer.add(group);
layer.add(circleA);
// add the shape to the layer
//layer.add(circle);
layer.add(arrow);
//layer.add(star);
// add the layer to the stage
stage.add(layer);
</script>
</body>
</html>
When you put a shape on a group, it's getX() and getY() functions return values relative to the origin point of the group. Your error was to assume that the X, Y position of the circle in the group would change as the group is dragged.
In the working code below, based on your posted code, I changed only the first line of the AdjustPoint() function so that the X, Y position of the circle has added to it the X, Y position of the group.
This fixes your issue.
Tip: as you start using groups be aware that their extent on the page is that of the minimum rectangle needed to contain the group shapes. If you want to specifically control the size and location of a group, add to it a Rect() shape of specific width and height to give a known size to the group.
I also added a call to that function at the end of the code so that the arrow joins when the code initially runs.
var width = window.innerWidth;
var height = window.innerHeight;
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height
});
var layer = new Konva.Layer();
var group = new Konva.Group({
x:120,
y:10,
draggable: true,
});
var circle = new Konva.Circle({
x: stage.getWidth() / 2,
y: 60,
radius: 40,
fill: 'green',
stroke: 'black',
strokeWidth: 2,
});
var circleA = new Konva.Circle({
x: stage.getWidth() / 5,
y: stage.getHeight() / 5,
radius: 30,
fill: 'red',
stroke: 'black',
strokeWidth: 2,
draggable: true
});
var arrow = new Konva.Arrow({
points: [circle.getX(), circle.getY(), circleA.getX(), circleA.getY()],
pointerLength: 10,
pointerWidth: 10,
fill: 'black',
stroke: 'black',
strokeWidth: 4
});
var star = new Konva.Star({
x: stage.getWidth() / 2,
y: 60,
numPoints: 5,
innerRadius: 30,
outerRadius: 50,
fill: '#89b717',
opacity: 0.8,
scale: {
x : 1.4,
y : 1.4
},
rotation: Math.random() * 180,
shadowColor: 'black',
shadowBlur: 10,
shadowOffset: {
x : 5,
y : 5
},
shadowOpacity: 0.6,
});
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height
});
function adjustPoint(e){
// changes here
var p=[ group.getX() + circle.getX(), group.getY() + circle.getY(), circleA.getX(), circleA.getY()];
// changes here
arrow.setPoints(p);
layer.draw();
stage.draw();
// console.log('group = ' + group.getX() + ', ' + group.getY());
// console.log('red circle = ' + circleA.getX() + ', ' + circleA.getY());
}
//circle.on('dragmove', adjustPoint);
group.on('dragmove', adjustPoint);
circleA.on('dragmove', adjustPoint);
group.add(star,circle);
//group.add(circle);
layer.add(group);
layer.add(circleA);
// add the shape to the layer
//layer.add(circle);
layer.add(arrow);
//layer.add(star);
// add the layer to the stage
stage.add(layer);
// changes here
adjustPoint();
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #F0F0F0;
}
<script src="https://cdn.rawgit.com/konvajs/konva/0.13.0/konva.min.js"></script>
<body>
<div id="container"></div>
</body>