I try to rotate an object around the canvas center smoothly using a slider,
I use fabric.util.rotatePoint to find the new center point and set the new angle,but it seems not exactly around the center point and the object position is jumping.
https://jsfiddle.net/j0g1tLsb/26/
here is my code doing this:
const canvas = new fabric.Canvas('c', { width: innerWidth, height: innerHeight });
canvas.controlsAboveOverlay = true;
canvas.preserveObjectStacking = true;
const rect = new fabric.Rect({
width: 300,
height: 150,
left: 400,
top: 400,
fill: "lightgray",
originX: 'center',
originY: 'center'
});
const centerPoint = new fabric.Circle({
originX: 'center',
originY: 'center',
top: innerHeight/2,
left: innerWidth/2,
radius: 10,
fill: 'red',
hasControls: false,
selectable:false
});
const log = new fabric.Text('', {
left: 30,
top: 30,
originX: 'left',
originY: 'top',
fontSize: 18,
evented: false,
selectable: false
});
canvas.add(rect, centerPoint, log);
document.getElementById('img-rotation').oninput = function() {
rect.set('angle', this.value);
var posNewCenter = fabric.util.rotatePoint(
rect.getCenterPoint(),
canvas.getVpCenter(),
fabric.util.degreesToRadians(this.value)
);
rect.set({
left: posNewCenter.x,
top: posNewCenter.y,
angle: this.value
});
log.set('text', `angle: ${Math.round(rect.angle)} \nleft: ${rect.left} \ntop: ${rect.top}`);
canvas.requestRenderAll();
};
rect.on('modified', () => {
log.set('text', `angle: ${Math.round(rect.angle)} \nleft: ${rect.left} \ntop: ${rect.top}`);
canvas.renderAll();
});
Your problem is in getting center point from the rectangle every time. You need just set new Point from the original rectangle position.
var posNewCenter = fabric.util.rotatePoint(
new fabric.Point(400, 400), //here is your mistake
canvas.getVpCenter(),
fabric.util.degreesToRadians(this.value)
);
rect.set({
left: posNewCenter.x,
top: posNewCenter.y,
angle: this.value
});
Working fiddle
UPDATE:
In order of the modification of the Rectangle you need to use object:modified event to reassign top and left coordinates.
First of all declare top and left variables and use them inside rectangle object:
let top = 400;
let left = 400;
const rect = new fabric.Rect({
...
left: left,
top: top,
...
targetObj: 'rectangle' //you can use any value or ID, it's only for targeting purpose
});
Then you need to check when object is modified. If event is fired then check if it is rectangle.
canvas.on('object:modified', (e) => {
if (e.target.hasOwnProperty('targetObj') && e.target.targetObj === 'rectangle') {
left = e.target.get('left');
top = e.target.get('top');
}
})
Finally, use left and top variables inside oninput event of the slider:
var posNewCenter = fabric.util.rotatePoint(
new fabric.Point(left, top),
canvas.getVpCenter(),
fabric.util.degreesToRadians(this.value)
);
Working updated fiddle
Related
Wondering if there is a way to align objects in FabricJs by their bounding box?
I'm using obj.getBoundingRect() function to determine objects bounders, then compare them with a bounding box (BB) coordinates of an Active one (that one which I move). If I see that something falls between some gap (let's say 10px) I assign an active object top to be the same top as a comparable element by using a .setTop() property.
The problem is that TOP is not a right attribute to use, since the top of the bounding box may differ between elements. For example, 2 elements with the same top but different angle will have different Bounding Box Top...
Hope you see my point...
https://jsfiddle.net/redlive/hwcu1p4f/
var canvas = this.__canvas = new fabric.Canvas('canvas');
//fabric.Object.prototype.transparentCorners = false;
var red = new fabric.Rect({
id: 1,
left: 100,
top: 50,
width: 100,
height: 100,
fill: 'red',
angle: 0,
padding: 10
});
canvas.add(red);
var green = new fabric.Rect({
id: 2,
left: 250,
top: 180,
width: 100,
height: 100,
fill: 'green',
angle: 45,
padding: 10
});
canvas.add(green);
canvas.renderAll();
canvas.on("object:moving", function(e){
const draggableObj = e.target;
const draggableObjBound = draggableObj.getBoundingRect();
canvas.forEachObject(function(obj) {
if (obj.id !== draggableObj.id) {
var bound = obj.getBoundingRect();
if (draggableObjBound.top > bound.top - 10 && draggableObjBound.top < bound.top + 10) {
draggableObj.setTop(obj.getTop());
}
}
});
});
canvas.forEachObject(function(obj) {
var setCoords = obj.setCoords.bind(obj);
obj.on({
moving: setCoords,
scaling: setCoords,
rotating: setCoords
});
});
canvas.on('after:render', function() {
canvas.contextContainer.strokeStyle = '#555';
canvas.forEachObject(function(obj) {
var bound = obj.getBoundingRect();
canvas.contextContainer.strokeRect(
bound.left,
bound.top,
bound.width,
bound.height
);
})
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.0.0-rc.3/fabric.js"></script>
<canvas id="canvas" width="800" height="500" style="border:1px solid #ccc"></canvas>
You should use the center to align them, that is not gonna change.
to align the bounding box at left 5 for example:
1) calculate bounding box.
2) set the position of the object to 5 + bb.width/2 considering center.
In this case the bounding rects get aligned.
var canvas = this.__canvas = new fabric.Canvas('canvas');
//fabric.Object.prototype.transparentCorners = false;
var red = new fabric.Rect({
id: 1,
left: 100,
top: 50,
width: 100,
height: 100,
fill: 'red',
angle: 0,
padding: 10
});
canvas.add(red);
var green = new fabric.Rect({
id: 2,
left: 250,
top: 180,
width: 100,
height: 100,
fill: 'green',
angle: 45,
padding: 10
});
canvas.add(green);
//ALIGN EVERYTHING TO 5
canvas.forEachObject(function(object) {
var bb = object.getBoundingRect();
object.setPositionByOrigin({ x: 5 + bb.width/2, y: bb.top }, 'center', 'center');
object.setCoords();
});
canvas.renderAll();
canvas.on("object:moving", function(e){
const draggableObj = e.target;
const draggableObjBound = draggableObj.getBoundingRect(true, true);
canvas.forEachObject(function(obj) {
if (obj.id !== draggableObj.id) {
var bound = obj.getBoundingRect(true, true);
if (draggableObjBound.top > bound.top - 10 && draggableObjBound.top < bound.top + 10) {
draggableObj.setPositionByOrigin({ x: draggableObj.left, y: bound.top + draggableObjBound.height/2 }, draggableObj.originX, 'center');
}
}
});
});
canvas.forEachObject(function(obj) {
var setCoords = obj.setCoords.bind(obj);
obj.on({
moving: setCoords,
scaling: setCoords,
rotating: setCoords
});
});
canvas.on('after:render', function() {
canvas.contextContainer.strokeStyle = '#555';
canvas.forEachObject(function(obj) {
var bound = obj.getBoundingRect(true, true);
canvas.contextContainer.strokeRect(
bound.left,
bound.top,
bound.width,
bound.height
);
})
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.0.0-rc.3/fabric.js"></script>
<canvas id="canvas" width="800" height="500" style="border:1px solid #ccc"></canvas>
My canvas has two objects. The first one is selectable true and the second one is selectable false. I am looking to manage the second object through the first object's action.
If the first object moves, then the second objects need to move in the same direction. If the first one rotates, then the second one rotates at the same angle in the second one's position.
I have tried adding events (moving,rotating) to the first selectable object, and then getting the required values from the first object and setting it to second object, but rotating is not working properly. My code is as follows:
imageOne.on('rotating', function (evt) { imageThree.angle = imageOne.getAngle(); });
var canvas = new fabric.Canvas('c');
var evented = false;
var rect1 = new fabric.Rect({
left: 50,
top: 60,
fill: 'blue',
width: 150,
height: 150,
});
var rect2 = new fabric.Rect({
left: 210,
top: 60,
fill: 'magenta',
width: 150,
height: 150,
selectable: false
});
canvas.add(rect1,rect2);
function rect1MouseDown(option){
this.mousesDownLeft = this.left;
this.mousesDownTop = this.top;
this.rect2Left = rect2.left;
this.rect2Top = rect2.top;
}
function rect1Move(option){
rect2.left = this.rect2Left+ this.left - this.mousesDownLeft ;
rect2.top = this.rect2Top+ this.top- this.mousesDownTop;
rect2.setCoords();
}
function rect1Rotating(options){
rect2.setAngle(this.angle);
}
register();
function register(){
if(evented) return;
rect1.on('moving', rect1Move);
rect1.on('mousedown', rect1MouseDown);
rect1.on('rotating', rect1Rotating);
evented = true;
}
function unRegister(){
rect1.off('moving');
rect1.off('mousedown');
rect2.on('rotating');
evented = false;
}
canvas {
border: blue dotted 2px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.19/fabric.min.js"></script>
<button onclick='register()'>Register Event</button>
<button onclick='unRegister()'>Unregister Event</button><br>
<canvas id="c" width="400" height="400"></canvas>
You can use obj.setAngle() for version <1.7.19 and obj.rotate() for version 2 , to set angle to an object.
i try to change the color of all selected objects on my canvas. I figured out that i can change properties of the group itself (this.canvas.getActiveGroup().setFill(...)).
Since i want to change different properties depending on the type of the selected object, i can't use this function. Instead i try to iterate over all selected objects and manipulate them.
this.canvas.getActiveGroup().getObjects().forEach(obj =>
{
if(obj instanceof fabric.Path)
{
obj.setStroke(color);
}
else
{
obj.setFill(color);
}
});
this.canvas.renderAll();
The color will be changed, but unfortunately after deselecting the objects all styles will be changed back.
I think the styles will be applied to the group, because when selecting again all elements will appear in the right color...
When i remove the activeGroup (this.canvas.discardActiveGroup()) before, it will work but will bring some other problems.
Btw.: the inspector (dev tools) displays the right color. After resizing a specific element it will also update its color.
Has anyone similar problems and knows a solution?
Best regards
Daniel
DEMO
var color = 'black';
function changeColor() {
var r = getRandomArbitrary(0, 225);
var g = getRandomArbitrary(0, 225);
var b = getRandomArbitrary(0, 225);
color = 'rgb(' + r + ',' + g + ',' + b + ')';
fillColorOb();
}
function getRandomArbitrary(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
var c = new fabric.Canvas('canvas');
c.add(new fabric.Circle({
left: 50,
top: 50,
radius: 50,
originX: 'center',
originY: 'center',
}))
c.add(new fabric.Circle({
left: 50,
top: 150,
radius: 50,
originX: 'center',
originY: 'center',
}))
c.add(new fabric.Circle({
left: 150,
top: 100,
radius: 50,
originX: 'center',
originY: 'center',
}))
c.renderAll();
function fillColorOb() {
var objs = c.getActiveGroup();
if(!objs) return;
objs.forEachObject(function(obj) {
if (obj instanceof fabric.Path) {
obj.setStroke(color);
} else {
obj.setFill(color);
}
c.renderAll();
});
}
fabric.Group.prototype._restoreObjectState = function(object) {
this.realizeTransform(object);
object.setCoords();
object.hasControls = object.__origHasControls;
delete object.__origHasControls;
object.set('active', false);
delete object.group;
object.dirty = true; // it will render on next renderAll() call
return this;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.16/fabric.min.js"></script>
<button type="button" name="button" onclick="changeColor();">color</button><br>
<canvas id="canvas" width="400" height="400"></canvas>
If I am , then this gonna work, check demo, add this function
fabric.Group.prototype._restoreObjectState = function(object) {
this.realizeTransform(object);
object.setCoords();
object.hasControls = object.__origHasControls;
delete object.__origHasControls;
object.set('active', false);
delete object.group;
object.dirty = true; // it will render on next renderAll() call
return this;
}
Any guidance with jsfiddle example showing ClipTo serialization will be appreciated? Current ToJSON function does not work when trying to serialize clipped objects. See the ToJSON implementation at the bottom of the code.
JSFiddle Link: http://jsfiddle.net/PromInc/ZxYCP/
var img01URL = 'https://www.google.com/images/srpr/logo4w.png';
var img02URL = 'http://fabricjs.com/lib/pug.jpg';
var canvas = new fabric.Canvas('c');
// Note the use of the `originX` and `originY` properties, which we set
// to 'left' and 'top', respectively. This makes the math in the `clipTo`
// functions a little bit more straight-forward.
var clipRect1 = new fabric.Rect({
originX: 'left',
originY: 'top',
left: 180,
top: 10,
width: 200,
height: 200,
fill: '#DDD', /* use transparent for no fill */
strokeWidth: 0,
selectable: false
});
// We give these `Rect` objects a name property so the `clipTo` functions can
// find the one by which they want to be clipped.
clipRect1.set({
clipFor: 'pug'
});
canvas.add(clipRect1);
var clipRect2 = new fabric.Rect({
originX: 'left',
originY: 'top',
left: 10,
top: 10,
width: 150,
height: 150,
fill: '#DDD', /* use transparent for no fill */
strokeWidth: 0,
selectable: false
});
// We give these `Rect` objects a name property so the `clipTo` functions can
// find the one by which they want to be clipped.
clipRect2.set({
clipFor: 'logo'
});
canvas.add(clipRect2);
function findByClipName(name) {
return _(canvas.getObjects()).where({
clipFor: name
}).first()
}
// Since the `angle` property of the Image object is stored
// in degrees, we'll use this to convert it to radians.
function degToRad(degrees) {
return degrees * (Math.PI / 180);
}
var clipByName = function (ctx) {
this.setCoords();
var clipRect = findByClipName(this.clipName);
var scaleXTo1 = (1 / this.scaleX);
var scaleYTo1 = (1 / this.scaleY);
ctx.save();
var ctxLeft = -( this.width / 2 ) + clipRect.strokeWidth;
var ctxTop = -( this.height / 2 ) + clipRect.strokeWidth;
var ctxWidth = clipRect.width - clipRect.strokeWidth;
var ctxHeight = clipRect.height - clipRect.strokeWidth;
ctx.translate( ctxLeft, ctxTop );
ctx.rotate(degToRad(this.angle * -1));
ctx.scale(scaleXTo1, scaleYTo1);
ctx.beginPath();
ctx.rect(
clipRect.left - this.oCoords.tl.x,
clipRect.top - this.oCoords.tl.y,
clipRect.width,
clipRect.height
);
ctx.closePath();
ctx.restore();
}
var pugImg = new Image();
pugImg.onload = function (img) {
var pug = new fabric.Image(pugImg, {
angle: 45,
width: 500,
height: 500,
left: 230,
top: 50,
scaleX: 0.3,
scaleY: 0.3,
clipName: 'pug',
clipTo: function(ctx) {
return _.bind(clipByName, pug)(ctx)
}
});
canvas.add(pug);
};
pugImg.src = img02URL;
var logoImg = new Image();
logoImg.onload = function (img) {
var logo = new fabric.Image(logoImg, {
angle: 0,
width: 550,
height: 190,
left: 50,
top: 50,
scaleX: 0.25,
scaleY: 0.25,
clipName: 'logo',
clipTo: function(ctx) {
return _.bind(clipByName, logo)(ctx)
}
});
canvas.add(logo);
};
logoImg.src = img01URL;
//convert to json
var serialized=JSON.stringify(canvas);
canvas.clear();
canvas.loadFromDatalessJSON(serialized);
alert(serialized);
fabricjs clipTo should be included in the json representation of the canvas by default.
So if you use toJSON you will find a clipTo field in the json representation of canvas containing the clipTo's function.
Here is a demo.
Why the rectangle is not showing up? I've found the examples at GitHub, official site etc. But they're not working for me.
<script type="text/javascript">
var fCanvas;
window.onload = function (){
var oldCanvas = document.getElementById('canvas');
fCanvas = new fabric.Canvas('canvas',
{
width: oldCanvas.parentNode.clientWidth,
height: oldCanvas.parentNode.clientHeight
});
}
function CreateGraphicObject(id){
//...
var rect = new fabric.Rect({
left: 100,
top: 100,
fill: 'red',
width: 20,
height: 20
});
fCanvas.add(rect);
//fCanvas.renderAll(); - did not help
}
</script>
Thanks.
I had created a fiddle. I hope it will help you.
https://jsfiddle.net/mullainathan/hgnvaqej/
You have created function but never called it, so the rectangle never gets created.
You just need to call the function, in your code I suppose the best place to call it after creating the canvas:
window.onload = function (){
var oldCanvas = document.getElementById('canvas');
fCanvas = new fabric.Canvas('canvas',
{
width: oldCanvas.parentNode.clientWidth,
height: oldCanvas.parentNode.clientHeight
});
CreateGraphicObject(); //I didn't pass any "id" because it makes no sense in the given code
}
function CreateGraphicObject(){
var rect = new fabric.Rect({
left: 100,
top: 100,
fill: 'red',
width: 20,
height: 20
});
fCanvas.add(rect);
fCanvas.renderAll();
}