So I want to save the canvas based on scale 1 and I want to include all existing/visible objects.
So right now, my canvas is 600x400 and if I were to save it, it would only save what's inside that 600x400, and it respects zoom level (a higher zoom will see less things, a lower zoom will see more things but smaller).
I've gotten around this by running this code:
let zoom = this.canvas.getZoom();
this.canvas.setZoom(1);
let url = this.canvas.toDataURL({width: 1000, height: 700, multiplier: 1});
this.canvas.setZoom(zoom);
window.open(
url,
'_blank' // <- This is what makes it open in a new window.
);
What this does is save the image as 1000x700, so stuff that were past 600px but under 1000 still gets saved.
However, this is hard coded. So I was wondering if there was an existing function or a clean/simple way to detect where all the objects are in and returns the full size (width and height).
fiddle: http://jsfiddle.net/1pxceaLj/3/
Update 1:
var gr = new this.fabric.Group([], {
left: 0,
top: 0
});
this.canvas.forEachObject( (o) => {
gr.add(o);
});
this.canvas.add(gr);
this.canvas.renderAll();
console.log(gr.height, gr.width); // 0 0
Solution
Using group was the best idea. Best example: http://jsfiddle.net/softvar/mRA8Z/
function selectAllCanvasObjects(){
var objs = canvas.getObjects().map(function(o) {
return o.set('active', true);
});
var group = new fabric.Group(objs, {
originX: 'center',
originY: 'center'
});
canvas._activeObject = null;
canvas.setActiveGroup(group.setCoords()).renderAll();
console.log(canvas.getActiveGroup().height);
}
One possibility would be to create a kind of Container using a fabricjs group and adding all created objects to this container.
I updated your fiddle here: http://jsfiddle.net/1pxceaLj/4/
Thus, you could just use group.width and group.height, perhaps adding a little offset or minimum values, and there you are having dynamical value to pass into toDataUrl even when having a smaller canvas.
code:
var canvas = new fabric.Canvas('c');
var shadow = {
color: 'rgba(0,0,0,0.6)',
blur: 20,
offsetX: 10,
offsetY: 10,
opacity: 0.6,
fillShadow: true,
strokeShadow: true
}
var rect = new fabric.Rect({
left: 100,
top: 100,
fill: "#FF0000",
stroke: "#000",
width: 100,
height: 100,
strokeWidth: 10,
opacity: .8
});
var rect2 = new fabric.Rect({
left: 800,
top: 100,
fill: "#FF0000",
stroke: "#000",
width: 100,
height: 100,
strokeWidth: 10,
opacity: .8
});
rect.setShadow(shadow);
//canvas.add(rect);
//canvas.add(rect2);
var gr = new fabric.Group([ rect, rect2 ], {
left: 0,
top: 0
});
canvas.add(gr);
function save()
{
alert(gr.width);
alert(gr.height);
let zoom = canvas.getZoom();
var minheight = 700;
canvas.setZoom(1);
let url = this.canvas.toDataURL({width: gr.width, height: gr.height > minheight ? gr.height : minheight, multiplier: 1});
canvas.setZoom(zoom);
window.open(
url,
'_blank'
);
}
Related
I'm experiencing an issue when scaling an image inside a group the offset of the image changes. The group consists of a background (Rect) and an image. When scaling the group the offset if the image becomes different. Expected is that the offset remains the same as the background.
I'm using Fabric 4.0.0-beta.6
const canvas = new fabric.Canvas('canvas');
canvas.setWidth(document.body.clientWidth);
canvas.setHeight(document.body.clientHeight);
const bg = new fabric.Rect({
width: 100,
height: 100,
fill: 'red',
});
fabric.Image.fromURL("https://dummyimage.com/300x150/000/fff", function(img) {
img.scaleToWidth(bg.width)
const post = new fabric.Group([bg, img], {
left: 100,
top: 100,
});
post.scaleToWidth(400);
canvas.add(post);
canvas.renderAll();
});
https://jsbin.com/migogekoxu/1/edit?html,css,js,output
Setting the strokeWidth property of the background element to 0 fixed the offset for me.
const canvas = new fabric.Canvas('canvas');
canvas.setWidth(document.body.clientWidth);
canvas.setHeight(document.body.clientHeight);
const bg = new fabric.Rect({
width: 100,
height: 100,
fill: 'red',
strokeWidth: 0, //<---
});
fabric.Image.fromURL("https://dummyimage.com/300x150/000/fff", function(img) {
img.scaleToWidth(bg.width)
const post = new fabric.Group([bg, img], {
left: 100,
top: 100,
});
post.scaleToWidth(400);
canvas.add(post);
canvas.renderAll();
});
<canvas id="canvas"></canvas>
<script src="https://cdn.jsdelivr.net/npm/fabric#4.0.0-beta.6-browser/dist/fabric.min.js"></script>
Obviously that's a bug with fabricJS. If you don't want to fix it on your own in it's source code you can workaround it however.
You can manually correct the position by adding an offset of 0.5 to the image's top and left properties.
For example:
const canvas = new fabric.Canvas('canvas');
const bg = new fabric.Rect({
width: 100,
height: 100,
fill: 'red'
});
fabric.Image.fromURL("https://dummyimage.com/300x150/000/fff", function(img) {
img.scaleToWidth(bg.width);
img.left += 0.5;
img.top += 0.5;
const post = new fabric.Group([bg, img], {
left: 100,
top: 100
});
post.scaleToWidth(400);
canvas.add(post);
canvas.renderAll();
});
<script src="https://cdn.jsdelivr.net/npm/fabric#4.0.0-beta.6-browser/dist/fabric.min.js"></script>
<canvas id="canvas" width="600" height="400"></canvas>
I have an issue on mobile orientation change. When the orientation changes the canvas size adjust himself accordingly. And objects in the canvas exist in the same position after orientation where they were before the orientation relatively the canvas left and top.
It will be more clear in the image.
I want to change the positions of the objects on window:resize relatively to the center of canvas instead of left and top. Please help!
You can center the object on canvas with canvas.centerObject(object);.
var canvas = new fabric.Canvas('c');
var dia1 = new fabric.Circle({
radius: 12,
left: 0,
top: 0,
originX: 'center',
originY: 'center',
fill: 'transparent',
strokeWidth: 5,
stroke: "red",
width: 50,
height: 50,
});
var dia2 = new fabric.Circle({
radius: 5,
left: 0,
top: 0,
originX: 'center',
originY: 'center',
fill: 'red',
width: 50,
height: 50,
});
var targetEl = new fabric.Group([dia1, dia2], {
originX: 'center',
originY: 'center',
});
//center object on canvas
canvas.centerObject(targetEl);
canvas.add(targetEl);
canvas.renderAll();
//center on window resize
$(window).resize(function() {
canvas.centerObject(targetEl);
canvas.renderAll();
});
canvas {
border: 1px solid #ccc;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.4.0/fabric.js"></script>
<canvas id="c" width="600" height="300"></canvas>
you may need to set the object dimensions as follwing
const canvas = new fabric.Canvas('canvas');
// you can simply use
canvas.viewportCenterObject(obj);
// or if you want to center manually
const centerObject = obj => {
const center = canvas.getCenter();
const zoom = canvas.getZoom();
canvas.centerObject(obj);
obj.set({
left: center.left / zoom - obj.width / 2,
top: center.top / zoom - obj.height / 2,
});
obj.setCoords();
canvas.calcOffset();
canvas.renderAll()
};
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.
I need to build a frame for my image in Fabric JS. This frame should be built dynamically to fit any size of the image.
To do this I use two images:
http://i.imgur.com/3VqKv1O.png - image for side
http://i.imgur.com/w41HYN3.png - image for corner
I've developed an algorithm, which does it and it works! That's great.
But I have a problem with the positioning of pieces of the frame.
As you can see in my example, there is strange space between pieces and they placed not on the same level by Y axis. I'm very surprised because these pieces have the same height and they have correct values for left and top.
Anybody knows what can be a reason of this problem?
Thanks in advance
JS Fiddle https://jsfiddle.net/Eugene_Ilyin/gzjxhLtm/9/
var sideObj = new fabric.Rect(),
frameSideTopImage = new fabric.Image(new Image(), {type: 'image'}),
frameCornerTLImage = new fabric.Image(new Image(), {type: 'image'}),
imageSize = 172,// Size of the image (width = height).
leftOffset = 100,
topOffset = 100;
// Load image for corner.
fabric.util.loadImage('http://i.imgur.com/w41HYN3.png', function (img) {
// Set image and options for top left corner.
frameCornerTLImage.setElement(img);
frameCornerTLImage.setOptions({
strokeWidth: 0,
left: leftOffset,
top: topOffset
});
// Load image for side.
fabric.util.loadImage('http://i.imgur.com/3VqKv1O.png', function (img) {
frameSideTopImage.setElement(img);
// Create canvas for pattern.
var canvasForFill = new fabric.StaticCanvas(fabric.util.createCanvasElement(), {enableRetinaScaling: false});
canvasForFill.add(frameSideTopImage);
// Configure top side for the frame.
sideObj.setOptions({
strokeWidth: 0,
objectCaching: false,
originX: 'left',
originY: 'top',
left: imageSize + 100,
top: 100,
width: 800,
height: imageSize,
fill: new fabric.Pattern({
source: function () {
canvasForFill.setDimensions({
width: imageSize,
height: imageSize
});
return canvasForFill.getElement();
},
repeat: 'repeat-x'
})
});
var canvas = new fabric.Canvas('c');
var frame = new fabric.Group([sideObj, frameCornerTLImage], {
strokeWidth: 0,
width: 800 * 3,
height: imageSize * 3,
scaleX: 1/3,
scaleY: 1/3,
}
);
canvas.add(frame);
canvas.renderAll();
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.7/fabric.js"></script>
<body>
<canvas id="c" width="1000" height="1000"></canvas>
</body>
var sideObj = new fabric.Rect(),
frameSideTopImage = new fabric.Image(new Image(), {type: 'image'}),
frameCornerTLImage = new fabric.Image(new Image(), {type: 'image'}),
imageSize = 172,// Size of the image (width = height).
leftOffset = 100,
topOffset = 100;
// Load image for corner.
fabric.util.loadImage('http://i.imgur.com/w41HYN3.png', function (img) {
// Set image and options for top left corner.
frameCornerTLImage.setElement(img);
frameCornerTLImage.setOptions({
left: leftOffset,
top: topOffset
});
// Load image for side.
fabric.util.loadImage('http://i.imgur.com/3VqKv1O.png', function (img) {
frameSideTopImage.setElement(img);
// Create canvas for pattern.
var canvasForFill = new fabric.StaticCanvas(fabric.util.createCanvasElement(), {enableRetinaScaling: false});
canvasForFill.add(frameSideTopImage);
// Configure top side for the frame.
sideObj.setOptions({
objectCaching: false,
originX: 'left',
originY: 'top',
left: imageSize + 100,
top: 100,
width: 800,
strokeWidth:0,
height: imageSize,
fill: new fabric.Pattern({
source: function () {
canvasForFill.setDimensions({
width: imageSize,
height: imageSize
});
return canvasForFill.getElement();
},
repeat: 'repeat-x'
})
});
var canvas = new fabric.Canvas('c');
canvas.add(sideObj);
canvas.add(frameCornerTLImage);
canvas.renderAll();
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.7/fabric.js"></script>
<body>
<canvas id="c" width="1000" height="1000"></canvas>
</body>
set strokeWidth:0 , strokeWidth is by default 1, so it creates a border of width 1 .
I have got a lot of helps from this site and contributors here, thanks. Now I have a question about the Rectangle in Fabric.js with stroke, as I used it as kind of placeholder for images and text, when I scaled it, the border line width is scaled too, I think it's kind a problem as I want to keep the border width not changed.
var canvas = new fabric.Canvas("c1");
var el = new fabric.Rect({
originX: "left",
originY: "top",
left: 5,
top: 5,
stroke: "#ccc",
strokWidth: 1,
fill: 'transparent',
opacity: 1,
width: 200,
height: 200,
cornerSize: 6
});
canvas.add (el);
canvas.renderAll ();
See example here http://jsfiddle.net/9yb46/, try scale it horizontally. And a image is here too, and see the left border and right border width, supposed as same as top and bottom border, but not:
This can be done so that you can scale independently.
In the scaling event check the width, height and scale factors, set the height and width to the new effective values and reset the scaleX and scaleY.
This quite probably will break other things that are scaled with the object so you'd have to handle those attributes in a similar fashion.
Demo Fiddle.
var canvas = new fabric.Canvas("c1");
var el = new fabric.Rect({
originX: "left",
originY: "top",
left: 5,
top: 5,
stroke: "rgb(0,0,0)",
strokeWidth: 1,
fill: 'transparent',
opacity: 1,
width: 200,
height: 200,
cornerSize: 6
});
el.on({
'scaling': function(e) {
var obj = this,
w = obj.width * obj.scaleX,
h = obj.height * obj.scaleY,
s = obj.strokeWidth;
obj.set({
'height' : h,
'width' : w,
'scaleX' : 1,
'scaleY' : 1
});
}
});
canvas.add (el);
canvas.renderAll ();
First of all you have miss-typed the name of the property in your fiddle : strokWidth - e is missing. But this is not the cause of the problem since the default value for the strokeWidth is 1.
The scaled stroke issue is the expected behavior and what you ask to do is not. Anyway, before you check my code, read here and here and maybe some more here.
Then try this code to help with your needs, this will work perfectly only if you keep the scale ratio of your rectangle as 1:1 (scaleX = scaleY).
This is jsfiddle:
var canvas = new fabric.Canvas("c1");
var el = new fabric.Rect({
originX: "left",
originY: "top",
left: 5,
top: 5,
stroke: "rgb(0,0,0)",
strokeWidth: 1,
fill: 'transparent',
opacity: 1,
width: 200,
height: 200,
cornerSize: 6
});
el.myCustomOptionKeepStrokeWidth = 1;
canvas.on({
'object:scaling': function(e) {
var obj = e.target;
if(obj.myCustomOptionKeepStrokeWidth){
var newStrokeWidth = obj.myCustomOptionKeepStrokeWidth / ((obj.scaleX + obj.scaleY) / 2);
obj.set('strokeWidth',newStrokeWidth);
}
}
});
canvas.add (el);
canvas.renderAll ();
Fabricjs now has a strokeUniform property on fabric.Rect that can be used to prevent the stroke width from mis-transforming.
When you set strokeUniform to false it will scale with the object if true it will match the pixel size of the stroke width.
var el = new fabric.Rect({
originX: "left",
originY: "top",
left: 5,
top: 5,
stroke: "#ccc",
strokWidth: 1,
fill: 'transparent',
opacity: 1,
width: 200,
height: 200,
cornerSize: 6,
strokeUniform: true
});