Related
I'm trying to check for a collision between 2 text objects and using the intersectsWithObject. It's working but it's taking the bouding rect into account. Is it possible to check on pixel level?
Current behaviour:
Wanted behaviour:
const canvas = new fabric.Canvas('canvas');
canvas.setWidth(document.body.clientWidth);
canvas.setHeight(document.body.clientHeight);
const text = new fabric.Textbox('some text', {
width: 300,
fontSize: 70,
top: 120,
left: 100
});
const text2 = new fabric.Textbox('some more text', {
width: 350,
fontSize: 50,
top: 200,
left: 20,
})
if (text.intersectsWithObject(text2, true, true)) {
text.set('fill', 'red');
}
else {
text.set('fill', 'black');
}
canvas.on('after:render', function() {
canvas.contextContainer.strokeStyle = '#555';
canvas.forEachObject(function(obj) {
var bound = obj.getBoundingRect();
canvas.contextContainer.strokeRect(
bound.left + 0.5,
bound.top + 0.5,
bound.width,
bound.height
);
})
});
canvas.add(text);
canvas.add(text2);
https://jsbin.com/menadejato/edit?js,console,output
I'm creating a shape with certain width and height (this shape is a clipping rect) inside canvas. Then inside this shape I'm loading an object which can be moved and rotated.
I wrote a custom function for get center point inside this shape, and it works good if object are not rotated.
If I rotate this object over 90deg and click "center" button - the object is moved up outside the clipping rect.
I cannot use a native FabricJs "centerV" function, because I would like that object will be centered inside clipping rect - not the canvas container, so that is why I made a variable "objectVerticalCenter".
My code is presented here:
var canvas = new fabric.Canvas('canvas', {
'selection': false
});
var clippingRect = new fabric.Rect({
left: 170,
top: 90,
width: 185,
height: 400,
fill: 'transparent',
stroke: 1,
opacity: 1,
hasBorders: false,
hasControls: false,
hasRotatingPoint: false,
selectable: false,
preserveObjectStacking: true,
objectCaching: false
});
var retinaScalling = canvas.getRetinaScaling();
var pol = new fabric.Polygon([{
x: 200,
y: 0
}, {
x: 250,
y: 50
}, {
x: 250,
y: 100
}, {
x: 150,
y: 100
}, {
x: 150,
y: 50
}], {
left: 250,
top: 150,
angle: 0,
fill: 'green'
});
pol.scaleX = 0.5;
pol.scaleY = 0.5;
pol.set('id', 'shape');
pol.scaleToWidth(clippingRect.getWidth());
pol.setCoords();
pol.clipTo = function(ctx) {
ctx.save();
ctx.setTransform(retinaScalling, 0, 0, retinaScalling, 0, 0);
clippingRect.render(ctx);
ctx.restore();
};
canvas.centerObjectH(pol);
canvas.add(pol);
canvas.renderAll();
canvas.setActiveObject(pol);
canvas.add(pol);
canvas.renderAll();
document.getElementById('rotate').onclick = function() {
var activeObject = canvas.getActiveObject();
activeObject.setAngle(200);
activeObject.setCoords();
canvas.renderAll();
};
document.getElementById('center').onclick = function() {
var activeObject = canvas.getActiveObject();
var rectHeight = activeObject.getBoundingRectHeight();
var objectVerticalCenter = (clippingRect.getHeight() / 2) - (rectHeight / 2) + clippingRect.top;
activeObject.set('top', objectVerticalCenter);
activeObject.setCoords();
canvas.renderAll();
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.21/fabric.min.js"></script>
<button id="rotate">Rotate</button>
<button id="center">Center</button>
<canvas id="canvas" width="530" height="600"></canvas>
JSFiddle
To reproduce my problem you have to, click "rotate", then "center" button and you will see that shape is moved up outside the clipping rect.
If object is not rotated centering works fine.
Is there any way to fix my centering function or way to use "centerV" function inside the clipping rect?
Regards
If you want to center an object inside another, the safest thing to do is:
get the center point of the container
set the center point of the object in that point
in code this could equal to:
var clipCenter = clippingRect.getCenterPoint();
activeObject.setPositionByOrigin(clipCenter,'center','center');
var canvas = new fabric.Canvas('canvas', {
'selection': false
});
var clippingRect = new fabric.Rect({
left: 170,
top: 90,
width: 185,
height: 400,
fill: 'transparent',
stroke: 1,
opacity: 1,
hasBorders: false,
hasControls: false,
hasRotatingPoint: false,
selectable: false,
preserveObjectStacking: true,
objectCaching: false
});
var retinaScalling = canvas.getRetinaScaling();
var pol = new fabric.Polygon([{
x: 200,
y: 0
}, {
x: 250,
y: 50
}, {
x: 250,
y: 100
}, {
x: 150,
y: 100
}, {
x: 150,
y: 50
}], {
left: 250,
top: 150,
angle: 0,
fill: 'green'
});
pol.scaleX = 0.5;
pol.scaleY = 0.5;
pol.set('id', 'shape');
pol.scaleToWidth(clippingRect.getWidth());
pol.setCoords();
pol.clipTo = function(ctx) {
ctx.save();
ctx.setTransform(retinaScalling, 0, 0, retinaScalling, 0, 0);
clippingRect.render(ctx);
ctx.restore();
};
canvas.centerObjectH(pol);
canvas.setActiveObject(pol);
canvas.add(pol);
canvas.renderAll();
document.getElementById('rotate').onclick = function() {
var activeObject = canvas.getActiveObject();
activeObject.setAngle(200);
activeObject.setCoords();
canvas.renderAll();
};
document.getElementById('center').onclick = function() {
var activeObject = canvas.getActiveObject();
var clipCenter = clippingRect.getCenterPoint();
activeObject.setPositionByOrigin(clipCenter,'center','center');
canvas.renderAll();
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.21/fabric.min.js"></script>
<button id="rotate">Rotate</button>
<button id="center">Center</button>
<canvas id="canvas" width="530" height="600"></canvas>
WARNING: turn the volume down before you run the snippet!
I want to be able to click on the stage to add a 'module' shape. But I have found that a click on the 'module' shape itself creates another, meaning that the stage.click listener is being fired when it should not be.
How can I have a stage.click listener that does not fire incorrectly when I click on a shape ?
var width = window.innerWidth;
var height = window.innerHeight;
var rectButtonClicked = false;
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height
});
var layer = new Konva.Layer();
var group = new Konva.Group({
draggable: true
});
stage.on('contentClick', function() {
createModule();
});
function createModule() {
var mouseX = stage.getPointerPosition().x;
var mouseY = stage.getPointerPosition().y;
var rect = new Konva.Rect({ //module rect
x: mouseX,
y: mouseY,
width: 100,
height: 50,
cornerRadius: 5,
fill: '#BEDBDD',
stroke: '#807C7B',
strokeWidth: 2,
draggable: true
});
group.add(rect);
var buttonRect = new Konva.Rect({ //button
x: mouseX+80,
y: mouseY+20,
width: 10,
height: 10,
cornerRadius: 1,
fill: 'blue',
stroke: '#807C7B',
strokeWidth: 1,
});
group.add(buttonRect)
var text = new Konva.Text({ //text on module
x: mouseX + 20,
y: mouseY + 20,
//fontFamily: 'Calibri',
fontSize: 16,
text: 'OSC',
fill: 'black'
});
group.add(text);
var randomFreq = getRandomInt();
var osc = new Tone.Oscillator(randomFreq, "sawtooth");
layer.add(group);
stage.add(layer);
buttonRect.on('click', function() {
rectButtonClicked = !rectButtonClicked;
if(rectButtonClicked){
osc.toMaster().start();
this.setFill('red');
} else {
osc.stop();
this.setFill('blue');
}
});
}
function getRandomInt() {
min = Math.ceil(100);
max = Math.floor(1000);
return Math.floor(Math.random() * (max - min)) + min;
}
var width = window.innerWidth;
var height = window.innerHeight;
//var drag = false;
var rectButtonClicked = false;
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height
});
var layer = new Konva.Layer();
var group = new Konva.Group({
draggable: true
});
stage.on('contentClick', function() {
createModule();
});
function createModule() {
var mouseX = stage.getPointerPosition().x;
var mouseY = stage.getPointerPosition().y;
var rect = new Konva.Rect({ //module rect
x: mouseX,
y: mouseY,
width: 100,
height: 50,
cornerRadius: 5,
fill: '#BEDBDD',
stroke: '#807C7B',
strokeWidth: 2,
draggable: true
});
group.add(rect);
var buttonRect = new Konva.Rect({ //button
x: mouseX+80,
y: mouseY+20,
width: 10,
height: 10,
cornerRadius: 1,
fill: 'blue',
stroke: '#807C7B',
strokeWidth: 1,
});
group.add(buttonRect)
var text = new Konva.Text({ //text on module
x: mouseX + 20,
y: mouseY + 20,
//fontFamily: 'Calibri',
fontSize: 16,
text: 'OSC',
fill: 'black'
});
group.add(text);
var randomFreq = getRandomInt();
var osc = new Tone.Oscillator(randomFreq, "sawtooth");
layer.add(group);
stage.add(layer);
buttonRect.on('click', function() {
rectButtonClicked = !rectButtonClicked;
if(rectButtonClicked){
osc.toMaster().start();
this.setFill('red');
} else {
osc.stop();
this.setFill('blue');
}
});
}
function getRandomInt() {
min = Math.ceil(100);
max = Math.floor(1000);
return Math.floor(Math.random() * (max - min)) + min;
}
<script src="https://tonejs.github.io/build/Tone.min.js"></script>
<script src="https://cdn.rawgit.com/konvajs/konva/1.7.6/konva.min.js"></script>
<div id="container"></div>
The stage.contentClick() listener is a special case to be used when you want the stage to listen to events on the stage content. However, the cancelBubble() function does not stop events bubbling from say a click on a shape to the stage.contentClick() listener.
To get the effect that you want, which is to give the impression that a click on the stage has happened, you need to add a rect that fills the stage and listen for events on that rect instead of the stage.
Below is a working example. The red background I added deliberately so you know there is something else above the stage. To remove this take out the fill color on the clickRect.
I also fixed up your buttons so that the contents are correctly grouped and drag together. You were almost correct but you needed the group to be created within in the createModule() function. You can see that I also made the group elements dragabble = false to complete the process.
I added a couple of console writes to show when the events fire.
[Also I got quite a shock when I switched on the tone for tone].
var width = window.innerWidth;
var height = window.innerHeight;
//var drag = false;
var rectButtonClicked = false;
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height
});
var layer = new Konva.Layer();
stage.add(layer);
var clickRect = new Konva.Rect({
x:0,
y:0,
width: width,
height: height,
fill: 'red',
stroke: '#807C7B',
strokeWidth: 2,
listening: 'true'
})
layer.add(clickRect);
clickRect.on('click', function() {
console.log('Stage click');
createModule();
});
function createModule() {
var group = new Konva.Group({ // move group create into createModule
draggable: true // we will make the elements not draggable - we drag the group
});
var mouseX = stage.getPointerPosition().x;
var mouseY = stage.getPointerPosition().y;
var rect = new Konva.Rect({ //module rect
x: mouseX,
y: mouseY,
width: 100,
height: 50,
cornerRadius: 5,
fill: '#BEDBDD',
stroke: '#807C7B',
strokeWidth: 2,
draggable: false // make the element not draggable - we drag the group
});
group.add(rect);
rect.on('click', function(evt){
console.log('Clicked on button');
})
var buttonRect = new Konva.Rect({ //button
x: mouseX+80,
y: mouseY+20,
width: 10,
height: 10,
cornerRadius: 1,
fill: 'blue',
stroke: '#807C7B',
strokeWidth: 1,
listening: true,
draggable: false // make the element not draggable - we drag the group
});
group.add(buttonRect)
var text = new Konva.Text({ //text on module
x: mouseX + 20,
y: mouseY + 20,
//fontFamily: 'Calibri',
fontSize: 16,
text: 'OSC',
fill: 'black',
draggable: false // make the element not draggable - we drag the group
});
group.add(text);
var randomFreq = getRandomInt();
var osc = new Tone.Oscillator(randomFreq, "sawtooth");
layer.add(group);
stage.add(layer);
buttonRect.on('click', function(evt) {
rectButtonClicked = !rectButtonClicked;
if(rectButtonClicked){
osc.toMaster().start();
this.setFill('red');
} else {
osc.stop();
this.setFill('blue');
}
});
}
function getRandomInt() {
min = Math.ceil(100);
max = Math.floor(1000);
return Math.floor(Math.random() * (max - min)) + min;
}
stage.draw(); // draw so we can see click rect.
<script src="https://tonejs.github.io/build/Tone.min.js"></script>
<script src="https://cdn.rawgit.com/konvajs/konva/1.7.6/konva.min.js"></script>
<div id="container" style="background-color: gold;"></div>
I am working at a project with fabric js. I tried to minimize my problem, so I'm hoping that the code isn't too messed up.
I am creating some Objects which are linked with each other:
A Line, which contains a Start and an Endpoint
A Circle, which is StartPoint of 1 line and Endpoint of another line
with this combination i can create different shapes(like a polygon) and modify my move-functions for them too.
When a Circle is dragged, the related Lines are scaling and moving too. (in my code you can move the lines too and the shape is resized after that, but i didnt put it into this example, bc this short extract should be enough to show what my problem is.)
I got a little example in jsfiddle: https://jsfiddle.net/bxgox7cr/
When you look at the ends of the lines, you can clearly see a cut, so the eye soon recognize, that this is not a connected shape but rather some lines which are close to each other. Is there a way to modify the look of the lines, that the shape looks "closed"?
Here is my code, i tried to put some comments, that it is easy to read:
var canvas = new fabric.Canvas('canvas');
fabric.Object.prototype.originX = fabric.Object.prototype.originY = 'center';
document.getElementById("canvas").tabIndex = 1000;
/** ------------creating a Line Object, which contains a start and an endpoint ------------**/
fabric.LineWithPoints = fabric.util.createClass(fabric.Line, {
initialize: function(points, options) {
options || (options = {});
this.callSuper('initialize', points, options);
options &&
this.set('type', options.type),
this.set('name', options.name),
this.set('start_point', options.start_point),
this.set('end_point', options.end_point),
this.set('current_x', options.current_x),
this.set('current_y', options.current_y)
},
setStartPointAndEndPoint: function(start_point, end_point) {
this.set({
start_point: start_point,
end_point: end_point
});
},
setValues: function(new_x1, new_y1, new_x2, new_y2) {
// console.log(this);
this.set({
x1: new_x1,
x2: new_x2,
y1: new_y1,
y2: new_y2
});
this.setCoords();
}
});
/**--- modifie the circle element, adding new functions for the movement of the object-------*/
fabric.LinePoint = fabric.util.createClass(fabric.Circle, {
initialize: function(options) {
options || (options = {});
this.callSuper('initialize', options);
options &&
this.set('subtype', 'line_point'),
this.set('x', this.left),
this.set('y', this.top)
},
setPointCoordinates: function(new_left, new_top) {
this.set({
x: new_left,
y: new_top,
left: new_left,
top: new_top
});
this.setCoords();
},
move: function(new_left, new_top) {
var wall_1 = this.line1;
var wall_2 = this.line2;
this.setPointCoordinates(new_left, new_top);
wall_1.setValues(wall_1.x1, wall_1.y1, this.getLeft(), this.getTop());
wall_2.setValues(this.getLeft(), this.getTop(), wall_2.x2, wall_2.y2);
canvas.renderAll();
},
});
/**------------------- Moving Function------------------------------------------------- */
canvas.on('object:moving', function(event) {
var object = event.target;
if (object.subtype == "line_point") {
object.move(object.getLeft(), object.getTop());
}
});
/**------------------------------ create functions for the objects -----------------------*/
function newCircleObject(left, top, wall_1, wall_2) {
var circle = new fabric.LinePoint({
left: left,
top: top,
strokeWidth: 2,
radius: 15,
fill: 'grey',
stroke: 'black',
opacity: 0.1,
perPixelTargetFind: true,
subtype: 'line_point',
includeDefaultValues: false
});
circle.hasControls = false;
circle.hasBorders = false;
circle.line1 = wall_1;
circle.line2 = wall_2;
return circle;
}
function newWallObject(coords) {
var wall = new fabric.LineWithPoints(coords, {
stroke: 'black',
strokeWidth: 6,
lockScalingX: true,
lockScalingY: true,
perPixelTargetFind: true,
subtype: 'line',
type: 'line',
padding: 10,
includeDefaultValues: false
});
wall.hasControls = false;
wall.hasBorders = false;
return wall;
}
/**------------------------------ adding the shapes--------------------------------*/
var wall_1 = newWallObject([100, 100, 100, 500]);
var wall_2 = newWallObject([100, 500, 500, 500]);
var wall_3 = newWallObject([500, 500, 500, 100]);
var wall_4 = newWallObject([500, 100, 100, 100]);
var end_point_1 = newCircleObject(wall_1.x1, wall_1.y1, wall_4, wall_1);
var end_point_2 = newCircleObject(wall_2.x1, wall_2.y1, wall_1, wall_2);
var end_point_3 = newCircleObject(wall_3.x1, wall_3.y1, wall_2, wall_3);
var end_point_4 = newCircleObject(wall_4.x1, wall_4.y1, wall_3, wall_4);
wall_1.setStartPointAndEndPoint(end_point_1.name, end_point_2.name);
wall_2.setStartPointAndEndPoint(end_point_2.name, end_point_3.name);
wall_3.setStartPointAndEndPoint(end_point_3.name, end_point_4.name);
wall_4.setStartPointAndEndPoint(end_point_4.name, end_point_1.name);
canvas.add(wall_1, wall_2, wall_3, wall_4, end_point_1, end_point_2, end_point_3, end_point_4);
Add strokeLineCap: 'round',:
function newWallObject(coords) {
var wall = new fabric.LineWithPoints(coords, {
stroke: 'black',
strokeWidth: 6,
lockScalingX: true,
lockScalingY: true,
perPixelTargetFind: true,
strokeLineCap: 'round',
subtype: 'line',
type: 'line',
padding: 10,
includeDefaultValues: false
});
wall.hasControls = false;
wall.hasBorders = false;
return wall;
}
I looked up: http://fabricjs.com/docs/fabric.Object.html#strokeLineCap
I am implementing drag and drop between two papers .But I am stuck with the syncing of offset of dragged element with cursor position as I have two papers in my html body.I have very minute experience with css which may be causing problem of positioning of elements.
Use Case:-
User clicks on element from paper 2 and starts dragging and go to paper 1.
On Pointer up a clone of that element is added to paper 1 on the position of cursor in paper 1.
My strategy to handle this is :-
When the user clicks mousedown
1.Dynamically create a div
2.Create a third paper, say call it "flypaper" in the new div
Make a copy of the element that you want to clone, and add it to "flypaper"
3.Create a mousemove listener that will move the div containing "flypaper" with the mouse
4.Add a mouseup event that will add a clone of the element to the "paper2" when the user releases the button.
5.Clean up the "flypaper" div and events.
<body>
<div id="paper" class="paper" style="border-style: solid;border-width: 5px;width:600px"></div>
<div id="paper2" class="paper" style="border-style: solid;border-width: 5px;width:600px;display:inline-block" ondrop="drop(event)" ondragover="allowDrop(event)"></div>
<script>
var graph = new joint.dia.Graph;
var paper = new joint.dia.Paper({
el: $('#paper'),
width: 600,
height: 200,
model: graph,
gridSize: 1
});
var rect = new joint.shapes.basic.Rect({
position: { x: 100, y: 30 },
size: { width: 100, height: 30 },
attrs: { rect: { fill: 'blue' }, text: { text: 'my box', fill: 'white' } }
});
graph.addCells([rect]);
////////////////////////////////////////////////////////
var graph2 = new joint.dia.Graph;
var paper2 = new joint.dia.Paper({
el: $('#paper2'),
width: 600,
height: 200,
model: graph2,
gridSize: 1
});
paper2.on('cell:pointerup',function (cellView, evt, x, y) {
var rect4 = new joint.shapes.basic.Rect({
position: { x: 10, y: 50 },
size: { width: 100, height: 30 },
attrs: { rect: { fill: 'blue' }, text: { text: 'my box', fill: 'white' } }
});
graph.addCells([rect4]);
});
paper2.on('cell:pointerdown',function (cellView, evt, x, y) {
$('body').append('<div id="flyPaper" class="box" style="position: fixed;z-index: 100;display:block;opacity:.7;"></div>');
var graph3 = new joint.dia.Graph;
var paper3 = new joint.dia.Paper({
el: $('#flyPaper'),
width: 600,
height: 200,
model: graph3,
gridSize: 1
});
var rect3 = new joint.shapes.basic.Rect({
position: { x: 10, y: 50 },
size: { width: 100, height: 30 },
attrs: { rect: { fill: 'blue' }, text: { text: 'my box', fill: 'white' } }
});
graph3.addCells([rect3]);
$('body').mousemove(function(e){
var mouseX = e.pageX; //get mouse move position
var mouseY = e.pageY;
$( "div.box" ).offset({ top: mouseY, left: mouseX });
// $('div.box',this).css({'top': boxPositionY,'left': boxPositionX})
});
});
var rect2 = new joint.shapes.basic.Rect({
position: { x: 10, y: 50 },
size: { width: 100, height: 30 },
attrs: { rect: { fill: 'blue' }, text: { text: 'my box', fill: 'white' } }
});
graph2.addCells([rect2]);
</script>
</body>
I had the same problem (and have clients who won't pay for rappid which adds this feature to jointjs). So here's a snippet that may help others (see below).
The steps are th same as you pointed out:
1.Dynamically create a div
2.Create a third paper, say call it "flypaper" in the new div Make a copy of the element that you want to clone, and add it to "flypaper"
3.Create a mousemove listener that will move the div containing "flypaper" with the mouse
4.Add a mouseup event that will add a clone of the element to the "paper2" when the user releases the button.
5.Clean up the "flypaper" div and events.
The solution to your problem was to use cellView.model.clone() to add the right element and then some computation with $.offset, $.width() & $.height() to get the right flyingpaper postion and to check if the drop event occured on the target paper.
View on codepen
<body>
<div id="paper" class="paper" style="border-style: solid;border-width: 5px;width:600px"></div>
<div id="paper2" class="paper" style="border-style: solid;border-width: 5px;width:600px;display:inline-block"></div>
<script>
// Canvas where sape are dropped
var graph = new joint.dia.Graph,
paper = new joint.dia.Paper({
el: $('#paper'),
model: graph
});
// Canvas from which you take shapes
var stencilGraph = new joint.dia.Graph,
stencilPaper = new joint.dia.Paper({
el: $('#stencil'),
height: 60,
model: stencilGraph,
interactive: false
});
var r1 = new joint.shapes.basic.Rect({
position: {
x: 10,
y: 10
},
size: {
width: 100,
height: 40
},
attrs: {
text: {
text: 'Rect1'
}
}
});
var r2 = new joint.shapes.basic.Rect({
position: {
x: 120,
y: 10
},
size: {
width: 100,
height: 40
},
attrs: {
text: {
text: 'Rect2'
}
}
});
stencilGraph.addCells([r1, r2]);
stencilPaper.on('cell:pointerdown', function(cellView, e, x, y) {
$('body').append('<div id="flyPaper" style="position:fixed;z-index:100;opacity:.7;pointer-event:none;"></div>');
var flyGraph = new joint.dia.Graph,
flyPaper = new joint.dia.Paper({
el: $('#flyPaper'),
model: flyGraph,
interactive: false
}),
flyShape = cellView.model.clone(),
pos = cellView.model.position(),
offset = {
x: x - pos.x,
y: y - pos.y
};
flyShape.position(0, 0);
flyGraph.addCell(flyShape);
$("#flyPaper").offset({
left: e.pageX - offset.x,
top: e.pageY - offset.y
});
$('body').on('mousemove.fly', function(e) {
$("#flyPaper").offset({
left: e.pageX - offset.x,
top: e.pageY - offset.y
});
});
$('body').on('mouseup.fly', function(e) {
var x = e.pageX,
y = e.pageY,
target = paper.$el.offset();
// Dropped over paper ?
if (x > target.left && x < target.left + paper.$el.width() && y > target.top && y < target.top + paper.$el.height()) {
var s = flyShape.clone();
s.position(x - target.left - offset.x, y - target.top - offset.y);
graph.addCell(s);
}
$('body').off('mousemove.fly').off('mouseup.fly');
flyShape.remove();
$('#flyPaper').remove();
});
});
</script>
</body>