FabricJS: How to manually set object width and height - javascript

I have a fabric rectangle and I want to change width & height manually. The problem is the next: When I first resize rect with scaling it makes resize as expected, but when after this I try to change the values in my inputs - the rectangle rerenders with wrong dimensions, but the width & height values in javascript object correct.
Code:
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.3/fabric.min.js"></script>
<canvas id="draw"></canvas>
width:<input type="number" id="w" value="50">
var w = document.getElementById('w');
var c = new fabric.Canvas('draw');
var rect = new fabric.Rect({
top: 50,
left: 50,
width: parseInt(w.value),
height: parseInt(h.value),
fill: 'lightblue',
type: 'rect'
});
c.add(rect);
c.renderAll();
c.on('object:scaling', function(e) {
var width = parseInt(e.target.width * e.target.scaleX);
w.value = width;
});
w.addEventListener('change', function(e) {
rect.set('width', parseInt(e.target.value));
rect.setCoords();
c.requestRenderAll();
});
screenshot
jsfiddle

Found a solution, maybe this will help someone. When we listen for a change of width/height, we also should take into account the current scale value.
w.addEventListener('change', (e) => {
const scale = rect.getObjectScaling();
rect.set('width', parseInt(e.target.value) / scale.scaleX);
rect.setCoords();
rect.requestRenderAll();
});

Related

Fabric JS clipPath: how to fit the image to the canvas after cropping?

I implemented image cropping using FabricJS and clipPath property.
The question is how do I make the image fit the canvas after cropping? I want the cropped image to fill the canvas area but can't figure out whether it's possible to do using fabric js.
So I want the selected part of the image to fit the canvas size after the user clicks the Crop button:
About the code: I draw a rectangle on the canvas and the user can resize and move it. After that, the user can click the Crop button and get a cropped image. But the cropped part stays the same size it was on the original image whereas I want it to scale and fit the canvas.
Note: I tried to use methods like scaleToWidth. Additionally, I used absolutePositioned set to true for the selection rectangle. I tried to scale to image with this property set to false, but it didn't help.
Please do not suggest using cropX and cropY properties for cropping instead of clipPath as they don't work properly for rotated images.
HTML:
<button>Crop</button>
<canvas id="canvas" width="500" height="500"></canvas>
JS:
// crop button
var button = $("button");
// handle click
button.on("click", function(){
let rect = new fabric.Rect({
left: selectionRect.left,
top: selectionRect.top,
width: selectionRect.getScaledWidth(),
height: selectionRect.getScaledHeight(),
absolutePositioned: true
});
currentImage.clipPath = rect;
canvas.remove(selectionRect);
canvas.renderAll();
});
var canvas = new fabric.Canvas('canvas');
canvas.preserveObjectStacking = true;
var selectionRect;
var currentImage;
fabric.Image.fromURL('https://www.sheffield.ac.uk/polopoly_fs/1.512509!/image/DiamondBasement.jpg', img => {
img.scaleToHeight(500);
img.selectable = true;
canvas.add(img);
canvas.centerObject(img);
currentImage = img;
canvas.backgroundColor = "#333";
addSelectionRect();
canvas.setActiveObject(selectionRect);
canvas.renderAll();
});
function addSelectionRect() {
selectionRect = new fabric.Rect({
fill: 'rgba(0,0,0,0.3)',
originX: 'left',
originY: 'top',
stroke: 'black',
opacity: 1,
width: currentImage.width,
height: currentImage.height,
hasRotatingPoint: false,
transparentCorners: false,
cornerColor: 'white',
cornerStrokeColor: 'black',
borderColor: 'black',
});
selectionRect.scaleToWidth(300);
canvas.centerObject(selectionRect);
selectionRect.visible = true;
canvas.add(selectionRect);
}
Here is my jsfiddle: https://jsfiddle.net/04nvdeb1/23/
I also faced this same issue for a few days. But i could not found any solution even i also found your SO question and github issue. I think clipPath is the best choice for clipping the image using a nice adjustable musk.
Demo Link
Github Repo Link
In the button callback function, you have to implement like this way
step 1: 1st you have to create an Image instance to hold the cropped image
let cropped = new Image();
Step 2: You have to use Canvas.toDataURL() to exports the canvas element to a dataurl image. Here wan want to export only the selection rectangle or mask rectangle, so we pass the mast rect left, top, width, and height
cropped.src = canvas.toDataURL({
left: rect.left,
top: rect.top,
width: rect.width,
height: rect.height,
});
Step 3: I the last step after the image loaded we clear the canvas. create new object with our cropped image, and set some option and rerender the canvas
cropped.onload = function () {
canvas.clear();
image = new fabric.Image(cropped);
image.left = rect.left;
image.top = rect.top;
image.setCoords();
canvas.add(image);
canvas.renderAll();
};
i actually marged Crop Functionality using FabricJs #soution2 with your problem.
Actually you don't have to use clipPath method. What you want is actually move the area you need to the corner, and change the size of canvas.
so the sodo code is as below:
fabricCanvas.setDimensions({
width: cropRectObject.getScaledWidth(),
height: cropRectObject.getScaledHeight()
})
imageObject.set({
left: -cropRectObject.left,
top: -cropRectObject.top
})
fabricCanvas.remove(cropRectObject);
tweaked your fiddle a bit to show this idea:
https://jsfiddle.net/tgy320w4/4/
hope this could help.
const { width, height, left = 0, top = 0 } = rect;
if (width == null || height == null) return;
const zoom = canvas.getZoom();
// toExact is a prototype for precision
const nw = (width * zoom).toExact(1);
const nh = (height * zoom).toExact(1);
const nl = (left * zoom).toExact(1);
const nt = (top * zoom).toExact(1);
// Apply
canvas.clipPath = o;
// Size
canvas.setWidth(nw);
canvas.setHeight(nh);
canvas.absolutePan(new fabric.Point(nl, nt));
canvas.remove(rect);

Centering and scaling canvas object inside another canvas object by width/height in Fabric.js

Goal: To center an object (horizontally and vertically) inside another object (rectangle or group) on canvas via Fabric.js or via Javascript which keeps the original object aspect ratio the same, but also not exceed the parent object width/height proportions?
The parent object (rectangle or group) won't be centered on the canvas element.
Here's the code I have so far: https://stackblitz.com/edit/angular-my8hky
My app.component.ts so far:
canvas: fabric.Canvas;
ngOnInit() {
this.canvas = new fabric.Canvas('canvas');
var rect = new fabric.Rect({
left: 100,
top: 50,
width: 300,
height: 200,
fill: '#eee'
});
this.canvas.add(rect);
fabric.Image.fromURL('https://angular.io/assets/images/logos/angular/logo-nav#2x.png', (img) => {
let bounds = rect.getBoundingRect();
let oImg = img.set({
left: bounds.left,
top: bounds.top,
width: rect.width
}).scale(1);
this.canvas.add(oImg).renderAll();
});
}
Not only is the new object not centered vertically, but also not centered horizontally if the rectangle object height is decreased (for example to 50px in height).
I realize I'm only declaring the inner image object width to be the same as the parent rectangle boundary width.
Current solution:
Parent rectangle width: 300 and height: 200:
Parent rectangle width: 300 and height: 50:
Desired solution:
Parent rectangle width: 300 and height: 200:
Parent rectangle width: 300 and height: 50:
Can anyone assist?
Figured it out.. Here's the StackBlitz:
https://stackblitz.com/edit/angular-pg4fis
The trick was to first add the rectangle to a Fabric group, like so:
var rect = new fabric.Rect({
left: 100,
top: 50,
width: 450,
height: 200,
fill: '#e3e3e3',
});
var rectGroup = new fabric.Group([rect], {
name: 'Rectangle',
});
this.canvas.add(rectGroup);
Then, I was able to establish the parent (group) element boundaries and use a variable scaling factor to set the image via Math functionality:
fabric.Image.fromURL('https://angular.io/assets/images/logos/angular/logo-nav#2x.png', (img) => {
let bounds = rectGroup.getBoundingRect();
const scaleFactor = Math.min(
Math.min(1, bounds.width / img.width),
Math.min(1, bounds.height / img.height)
);
img.scale(scaleFactor);
img.set({
top: bounds.top + Math.max(bounds.height - img.height * scaleFactor, 0)/2,
left: bounds.left + Math.max(bounds.width - img.width * scaleFactor, 0)/2,
});
rectGroup.addWithUpdate(img);
this.canvas.renderAll();
});

Pixi.js - Re-positioning element after window resize

I'm quite a noob with Pixi.js so here comes the noob question.
I'm trying to display a square in the middle of the screen. When I resize the window, the square should stay in the middle.
When drawing the square I'm using
const square = new PIXI.Graphics();
square.beginFill(0xff0000, 1);
square.drawRect(app.renderer.width /2, app.renderer.height / 2, 50, 50);
square.endFill();
app.stage.addChild(square);
Then, I understand that the x and y properties of square are relative to the initial drawing position. So square.x = 0 will keep the square in the middle.
But then, how do I reset the center of the window (and so the position of the square) when I resize it?
This can be your solution. This is diametric resize response.
I use something like :
getWidthByPer : function (per) {
return window.innerWidth / 100 * per
}
console.log = function(){};
var app = new PIXI.Application({
autoResize: true,
resolution: 1.77
});
document.querySelector("#MYAPP").appendChild(app.view);
const rect = new PIXI.Graphics()
.beginFill(0x55faaf)
.drawRect(-50, -50, 100, 100);
app.stage.addChild(rect);
window.addEventListener('resize', resize);
function resize() {
const parent = app.view.parentNode;
app.renderer.resize(parent.clientWidth, parent.clientHeight);
rect.position.set(
app.screen.width / 2 ,
app.screen.height / 2
);
}
window.onload = function(){
resize();
};
canvas {
display:block;
}
#MYAPP {
position: absolute;
overflow: hidden;
top:0%;
left:0%;
width: 100%;
height: 100%;
}
<script src="https://pixijs.download/v4.7.0/pixi.min.js"></script>
<div id="MYAPP"></div>
I made adaptive-scale to reuse common resizing needs for multiple projects

Increasing d3 SVG container size

I am trying to increase the size of my SVG container dynamically so that it fits all the data. There is a fiddle which explains the dynamic increase of the SVG: http://jsfiddle.net/CKW5q/
However, the same concept doesn't work for a bi-directional sankey chart(d3). Following is the function called to expand the parentnode:
function expand(node) {
node.state = "expanded";
node.children.forEach(function (child) {
child.state = "collapsed";
child._parent = this;
child.parent = null;
containChildren(child);
}, node);
HEIGHT = HEIGHT + node.children.length * 10; //update height by 10px per child
WIDTH = WIDTH + node.children.length * 10;//update width
}
svg.attr("height", HEIGHT); // update svg height
This gives very odd results. It definitely increases the container, but unfortunately keeps the original SVG dimensions intact:
I suppose that the SVG HEIGHT and WIDTH being declared at the start of the script needs to be updated, which for some reasons am not able to. Any help will be appreciated.
You will need to do something like this:
var Chart = (function(window, d3) {
(init code that doesn't change on resize here)
render();
function render() {
updateDimensions();
(code that needs to be re-rendered on resize here)
}
function updateDimensions() {
margin = {top: 100, right: 40, bottom: 80, left: 80}; // example
width = window.innerWidth - margin.left - margin.right;
height = window.innerHeight - margin.top - margin.bottom;
}
return {
render: render
}
})(window, d3);
window.addEventListener('resize', Chart.render);

fabric.js - get New Height after redimensioning

I create group with:
var text = new fabric.Text(textValue,{left: 20, top: 30, fontSize:25});
var rect = new fabric.Rect({
width : text.get('width') + 40,
fill : "#FFFFFF",
height : 100,
stroke : '#000000',
strokeWidth : 1
});
var myGroupObj = new fabric.CustomGroup([ rect,text ], {
left : 0,
top : 0,
});
Now whene I manually resizes my group and I want to get a new heigth.
myGroupObj.get('height')
I have always last one (100).
Somme idea to get new height, please?
Instead of accessing the .height attribute, you should call the getHeight() function, which gives you the actual height (the one you see on the screen) of a fabric.js object. In the code you have created on jsfiddle, to get the real height, you should change the line alert(activeObject.height); by alert(activeObject.getHeight()); and you will see the difference.
Cheers,
Gonzalo Gabriel
I find solution to get New Height of object after manually resizes http://jsfiddle.net/islyoung2/7Tf22/4/.
var newHeight = myGroupObj.get('height') * myGroupObj.get('scaleY')
Thank you.
When you set the new width or the new height, don't forget to update also scaleX and scaleY:
canvas.on("object:modified", function (e) {
var activeObject = e.target;
if (!activeObject) {
return;
}
var newWidth = activeObject.width * activeObject.scaleX ;
var newHeight = activeObject.height * activeObject.scaleY ;
activeObject.width = newWidth;
activeObject.height = newHeight;
activeObject.scaleX = 1;
activeObject.scaleY = 1;
});

Categories

Resources