FabricJS - Why rotated object inside shape are not vertically centered? - javascript

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>

Related

scale image by scaling rectangle without group fabric.js

I am new in fabric.js and want to achieve one functionality
I added image and one rectangle same size of image, now I want to scale image while scaling rectangle.
I don't want to group objects , image should scale by scaling rectangle with previous left, top position of image
below is my code
var imgURL = 'http://i.imgur.com/8rmMZI3.jpg';
var canvas = new fabric.Canvas('canvas');
var pug;
var pugImg = new Image();
pugImg.onload = function (img) {
pug = new fabric.Image(pugImg, {
angle: 0,
width: 500,
height: 500,
left: 50,
top: 70,
scaleX: .25,
scaleY: .25,
selectable: false
});
var rect = new fabric.Rect({
top: 65,
left: 200,
width: 150,
height: 150,
fill: 'red',
});
canvas.sendToBack(rect);
canvas.bringToFront(pug);
canvas.add(rect);
canvas.add(pug);
canvas.renderAll();
};
pugImg.src = imgURL;
canvas.on('object:scaling', function(e) {
var bounds = e.target.getBoundingRect();
pug.set({
width: bounds.width,
height: bounds.height,
top: bounds.top,
left: bounds.left
})
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.2.0/fabric.js"></script>
<canvas id="canvas" width=400 height=400></canvas>
I added image and one rectangle same size of image
In your example the rectangle is not the same size as the image
rect = {
width: 150,
height: 150,
scaleX: 1,
scaleY: 1,
}
pug = {
width: 500,
height: 500,
scaleX: .25,
scaleY: .25
});
To achieve the desired effect, you need to give theme the same (width, height, scaleX, scaleY), then just copy the rect (scaleX, scaleY) to the pug. otherwise
if they're not the same size you'll need to compute the scale ratio between the two.
var imgURL = 'http://i.imgur.com/8rmMZI3.jpg';
var canvas = new fabric.Canvas('canvas');
var pug, rect;
var pugImg = new Image();
pugImg.onload = function (img) {
pug = new fabric.Image(pugImg, {
angle: 0,
width: 500,
height: 500,
left: 50,
top: 70,
scaleX: .25,
scaleY: .25,
selectable: false
});
rect = new fabric.Rect({
top: 65,
left: 200,
width: 500,
height: 500,
scaleX: .25,
scaleY: .25,
fill: 'red',
});
canvas.sendToBack(rect);
canvas.bringToFront(pug);
canvas.add(rect);
canvas.add(pug);
canvas.renderAll();
};
pugImg.src = imgURL;
canvas.on('object:scaling', function(e) {
pug.set({
scaleX: rect.scaleX,
scaleY: rect.scaleY,
})
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.2.0/fabric.js"></script>
<canvas id="canvas" width=400 height=400></canvas>

Why does FabricJS scale pattern differently on different devices/browsers?

See Fiddle: I am trying to render a background image on a FabricJS polygon, but for some reason the scale of the pattern is different on different devices / browsers for no obvious reason.
Here is the code:
HTML:
<canvas id="tcanvas" width="200" height="200" />
JavaScript:
function testPattern(id) {
var url = 'https://dummyimage.com/200x200/aff/000',
tCanvas = new fabric.Canvas(id);
new fabric.Image.fromURL(url, function(img) {
var tPoints = [{
x: 0,
y: 0
},
{
x: 0,
y: 200
},
{
x: 200,
y: 200
},
{
x: 200,
y: 0
}
];
var tPatternWidth = 200;
var tPatternHeight = 200;
var tImgXScale = tPatternWidth / img.width;
var tImgYScale = tPatternHeight / img.height;
img.set({
left: 0,
top: 0,
scaleX: tImgXScale,
scaleY: tImgYScale
});
var tPatternSourceCanvas = new fabric.StaticCanvas();
tPatternSourceCanvas.add(img);
tPatternSourceCanvas.renderAll();
var tPattern = new fabric.Pattern({
offsetX: 0,
offsetY: 0,
source: function() {
tPatternSourceCanvas.setDimensions({
width: tPatternWidth,
height: tPatternHeight
});
tPatternSourceCanvas.renderAll();
return tPatternSourceCanvas.getElement();
},
repeat: 'no-repeat'
});
var tImgPoly = new fabric.Polygon(tPoints, {
left: 100,
top: 100,
originX: 'center',
originY: 'center',
fill: tPattern,
objectCaching: false,
selectable: false,
stroke: 'red',
strokeWidth: 1
});
tCanvas.add(tImgPoly);
tCanvas.renderAll();
});
}
testPattern('tcanvas');
This is what I see on my desktop Chrome:
... and this what I see on a (Samsung) tablet Chrome:
How can I fix the code so that the both patterns look the same, i.e., like the first one?
It s a retina support side effect.
When initializing the canvas for the Pattern, please pass as option:
enableRetinaScaling: false
var tPatternSourceCanvas = new fabric.StaticCanvas(null, {enableRetinaScaling: false});
This will avoid fabric trying to enhance the canvas twice.

Set listener for Stage click

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>

FabricJS animating :selected object on canvas during mouse:up and :down

I am trying to use FabricJS to animate any object when it is selected.
So when selected an object 'shrinks' from center on mouse:down (10px off the width and 10px off the height)
And on mouse:up it returns to the original size and state.
I don't know if I need to use FabricJS built in animation for this. I wanted it to grow and shrink linear though.
Right now my Fiddle does pretty much nothing, I can't get far enough without errors.
Does anyone have any examples of this or similar? I couldn't find anything about changing the appearance of FabricJS objects on mouse:up/:down when selected.
(function() {
var canvas = this.__canvas = new fabric.Canvas('canvas');
var touchedObject;
var rect = new fabric.Rect({
left: 50,
top: 50,
width: 50,
height: 50,
fill: 'blue',
hasBorders: false,
});
canvas.add(rect);
var rect2 = new fabric.Rect({
left: 190,
top: 50,
width: 50,
height: 50,
fill: 'red',
hasBorders: false,
});
canvas.add(rect2);
canvas.on('object:selected', function(evn) {
/*animate({
'width': ,
'height':
}, {
duration: 200,
});*/
});
canvas.renderAll();
})();
You could accomplish that using the fabric.util.animate() method.
(function() {
var canvas = this.__canvas = new fabric.Canvas('canvas');
var touchedObject;
var rect = new fabric.Rect({
left: 50,
top: 50,
width: 50,
height: 50,
fill: '#07C',
hasBorders: false,
});
canvas.add(rect);
// disable controls and set hover-cursor
canvas.forEachObject(function(o) {
o.hasBorders = o.hasControls = false;
});
canvas.hoverCursor = 'pointer';
// mouse events
canvas.on('mouse:down', function(e) {
animate(e, 1);
});
canvas.on('mouse:up', function(e) {
animate(e, 0);
});
function animate(e, p) {
if (e.target) {
fabric.util.animate({
startValue: e.target.get('height'),
endValue: e.target.get('height') + (p ? -10 : 50 - e.target.height),
duration: 200,
onChange: function(v) {
e.target.setHeight(v);
canvas.renderAll();
},
onComplete: function() {
e.target.setCoords();
}
});
fabric.util.animate({
startValue: e.target.get('width'),
endValue: e.target.get('width') + (p ? -10 : 50 - e.target.width),
duration: 200,
onChange: function(v) {
e.target.setWidth(v);
canvas.renderAll();
},
onComplete: function() {
e.target.setCoords();
}
});
fabric.util.animate({
startValue: e.target.get('top'),
endValue: e.target.get('top') + (p && 5),
duration: 200,
onChange: function(v) {
e.target.setTop(v);
canvas.renderAll();
},
onComplete: function() {
e.target.setCoords();
}
});
fabric.util.animate({
startValue: e.target.get('left'),
endValue: e.target.get('left') + (p && 5),
duration: 200,
onChange: function(v) {
e.target.setLeft(v);
canvas.renderAll();
},
onComplete: function() {
e.target.setCoords();
}
});
}
}
canvas.renderAll();
})();
#canvas {
border: 1px solid lightgrey;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.4.0/fabric.min.js"></script>
<canvas id="canvas" width="150"></canvas>

clone one canvas to another having trouble

I have checked all the answers to clone existing canvas to another one. But I could not get it done.
Please check my current progress.
http://jsfiddle.net/37n8rtdf/5/
First canvas will be clipped to that square you see initially and content of that canvas will be added in another canvas. But I don't know it always throw TYPE_MISMATCH_ERR: DOM Exception 171 in chrome. I am using fabricjs to clipping content.
Bit of help is appreciated.
Thanks
Here is code of my script:
HTML
<textarea id="line_1"></textarea>
<input type="button" id="render" value="Apply" />
<input type="button" id="preview" value="preview" />
<canvas id="c" width="500" height="500" style="border:1px solid #ccc"></canvas>
<canvas id="c_new" width="500" height="500" style="border:1px solid red; margin: 30px;"></canvas>
`
Javascript
var canvas = this.__canvas = new fabric.Canvas('c');
var canvas_new = this.__canvas = new fabric.Canvas('c_new');
var product_image = 'http://www.jail.se/hardware/digital_camera/canon/ixus_800is-powershot_sd700/images/sample_photos/sample3.jpg';
//var ctx = canvas.getContext("2d");
var polygon;
$(document).ready(function(){
fabric.Object.prototype.transparentCorners = false;
//canvas.setDimensions({width:w,height:h});
var center = canvas.getCenter();
canvas.setBackgroundImage(product_image,
canvas.renderAll.bind(canvas), {
scaleX:1,
scaleY:1,
top: center.top,
left: center.left,
originX: 'center',
originY: 'center',
backgroundImageOpacity: 0,
backgroundImageStretch: false
});
canvas_new.setBackgroundImage(product_image,
canvas_new.renderAll.bind(canvas_new), {
scaleX:1,
scaleY:1,
top: center.top,
left: center.left,
originX: 'center',
originY: 'center'
});
polygon = new fabric.Polygon([
{x: 0, y: 0},
{x: 220, y: 0},
{x: 220, y: 180},
{x: 0, y: 180} ], {
left: 140,
top: 150,
angle: 0,
fill: 'transparent',
stroke: '#000', strokeWidth: 1,
lockMovementX: true,
lockMovementY: true,
lockScalingX: true,
lockScalingY: true,
lockRotation: true,
hasControls: false,
hasBorders: false,
hoverCursor: 'default',
overflow: 'hidden'
});
canvas.add(polygon);
$('#render').click(function(){ return render(); });
$('#preview').click(function(){ return rasterize(); });
});
function render()
{
var text_val = $('#line_1').val();
var comicSansText = new fabric.Text(text_val, {
fontWeight: 'normal'
});
canvas.add(comicSansText.set({ left: 200, top: 150, angle: 0 }));
}
function rasterize()
{
var shape = canvas.item(0);
polygon.strokeWidth=0;
canvas.renderAll();
//canvas.remove(shape);
canvas.clipTo = function(ctx) {
shape.render(ctx);
};
var ctx2 = canvas_new.getContext('2d');
ctx2.drawImage(canvas, 0, 0);
}
`
#WinterMute is right, you are trying to draw the Fabric object instead of the canvas element.
You fabric object seems to have two canvases embedded (lowerCanvasEl and upperCanvasEl).
So you can modify just a little bit your code to make it look like :
function rasterize() {
var shape = canvas.item(0);
polygon.strokeWidth = 0;
canvas.renderAll();
canvas.clipTo = function(ctx) {
shape.render(ctx);
};
var ctx2 = canvas_new.getContext('2d');
ctx2.drawImage(canvas.lowerCanvasEl, 0, 0);
ctx2.drawImage(canvas.upperCanvasEl, 0, 0);
}
Updated fiddle

Categories

Resources