How to make width and height of a rectangle updates after resize in fabric js? - javascript

I want to contain the rectangles within the image and wrote the following code. It does not work after resize because the width and height of the active object doesn't seem to update after resize.
This is the jsfiddle: https://jsfiddle.net/yxchng/0hL2khro/191/
canvas.on("object:moving", function(opt) {
activeObject = canvas.getActiveObject()
if (activeObject.left < 0) {
activeObject.left = 0;
}
if (activeObject.top < 0) {
activeObject.top = 0;
}
if (activeObject.left + activeObject.width > 1000) {
activeObject.left = 1000 - activeObject.width;
}
if (activeObject.top + activeObject.height > 1000) {
activeObject.top = 1000 - activeObject.height;
}
activeObject.setCoords();
});
Is there a better way to contain objects within image?

If you consider only scaling (not skewing);
Updated values will be
height = object.height * object.scaleY;
width = object.width * object.scaleX;
For fabricjs V 2.x
Use getScaledHeight and getScaledWidth which returns height and width of object bounding box counting transformations respectively.

Related

Javascript scale images to fit container and to equal height

I'm trying to build a gallery. The idea is to fit images into fixed width container, but images must be of the same height and preserve original aspect ratio, so they just need to scale somehow.I came up with a solution of my own, but it, sometimes,gives clunky results for images total width that are too small or too large to fit container.Also resulting widths are, for some reason, floating point values. Could someone help me to figure out more optimal way to do it?
My clunky solution: https://codepen.io/fuzzy-toozy/pen/wvEaorW
function recalcGallery() {
if (uploadedImages.length > 0) {
let min = galleryHeight;
for (let i = 0; i < uploadedImages.length; i++ ) {
let currentUploadedImages = uploadedImages[i];
// find element with smallest height
currentUploadedImages.forEach((val) => { if (min > val.height) { min = val.height; }});
let imgCont = [];
let totalWidth = 0;
// set all elements to same height
for (let j = 0; j < currentUploadedImages.length; j++) {
let imgContainer = document.querySelector(`[image-index${i}="${j + 1}"]`);
imgContainer.style.height = `${min}px`;
imgCont.push(imgContainer);
totalWidth += imgContainer.clientWidth;
}
if (totalWidth > galleryWidth) {
// calculate value to decrease height by based on percent of overflow
let decPx = Math.ceil(min - min * (galleryWidth) / totalWidth);
imgCont.forEach((val, i) => {
val.style.height = `${val.clientHeight - decPx}px`;
});
}
}
}
}
Your solution involves finding the element with the smallest height, setting all elements to the same height, and then checking if the total width of the images is greater than the gallery width. If the total width is greater, you calculate a value to decrease the height by based on the percentage of overflow and then decrease the height of each image container.
One potential issue with your solution is that you are relying on the clientWidth property of the image containers to calculate the total width of the images. This property can include padding and borders, which may not accurately reflect the total width of the images. A more accurate approach would be to use the naturalWidth property of the image elements, which reflects the actual width of the image.
Here is an example of how you could modify your code to use the naturalWidth property:
function recalcGallery() {
if (uploadedImages.length > 0) {
let min = galleryHeight;
for (let i = 0; i < uploadedImages.length; i++ ) {
let currentUploadedImages = uploadedImages[i];
// find element with smallest height
currentUploadedImages.forEach((val) => { if (min > val.height) { min = val.height; }});
let imgCont = [];
let totalWidth = 0;
// set all elements to same height
for (let j = 0; j < currentUploadedImages.length; j++) {
let imgElement = document.querySelector(`[image-index${i}="${j + 1}"] img`);
let aspectRatio = imgElement.naturalWidth / imgElement.naturalHeight;
let imgContainer = document.querySelector(`[image-index${i}="${j + 1}"]`);
imgContainer.style.height = `${min}px`;
imgContainer.style.width = `${min * aspectRatio}px`;
imgCont.push(imgContainer);
totalWidth += imgContainer.clientWidth;
}
if (totalWidth > galleryWidth) {
// calculate value to decrease height by based on percent of overflow
let decPx = Math.ceil(min - min * (galleryWidth) / totalWidth);
imgCont.forEach((val, i) => {
val.style.height = `${val.clientHeight - decPx}px`;
val.style.width = `${(val.clientHeight - decPx) * (val.querySelector('img').naturalWidth / val.querySelector('img').naturalHeight)}px`;
});
}
}
}
}
In this modified code, we calculate the aspect ratio of each image using the naturalWidth and naturalHeight properties, and set the width of each image container accordingly. We then use the clientWidth property of the image containers to calculate the total width of the images, but we use the naturalWidth property of the image elements to calculate the width of each image container when we need to adjust the height of the images.
Note that this code assumes that the images are contained within an tag within each image container. If you are using a different approach to display the images, you may need to modify the code accordingly.

How to resize a canvas properly in React using p5js?

I'm currently working on a project and one part is about resizing the model in the canvas once the canvas is resized (or window is resized). I have checked documentation for resizeCanvas() and applied it.
I'm first finding the ratio by dividing the currentWidth/defaultWidth which are currentWidth of the user's device and the default/selected width by us, respectively.
findRatio(wd, hd, wc, hc) {
const w = wc / wd;
const h = hc / hd;
return w < h ? w : h;
}
and in my windowResized() function, I'm setting width/height once canvas is resized on window resize.
export const windowResized = (p5) => {
if (p5) {
initVariables.browserWidth = p5.windowWidth;
initVariables.browserHeight = p5.windowHeight;
p5.resizeCanvas(initVariables.browserWidth, initVariables.browserHeight);
for (let m of initVariables.mM) {
m.updateonResize();
}
}
};
here initVariables is just an object with some bunch of variables.
I also have updateonResize() function in my parent class that triggers once window is resized.
updateonResize() {
this.r = this.findRatio(
this.initVariables.width,
this.initVariables.height,
this.initVariables.browserWidth,
this.initVariables.browserHeight
);
this.arr.push(this.r);
if (this.arr[this.arr.length - 2] !== undefined) {
if (this.r < this.arr[this.arr.length - 2]) {
console.log("canvas getting larger, not sure if I need to divide or multiply here");
this.min = this.min * this.r;
this.max = this.max * this.r;
this.measure = this.measure * this.r;
} else {
this.min = this.min * this.r;
this.max = this.max * this.r;
this.measure = this.measure * this.r;
}
}
}
Here, min, max, and measure variables are variables to detect the size of object parts which are the length of lines. If the canvas is getting smaller, you need to multiply measure value with ratio (where ratio is usually less than 1).
Problem1:
I'm having a problem when I go to window mode from full-screen mode in my Chrome browser. It does not trigger the windowOnResize() function. Is it possible to automatically trigger this function once you go to window mode?
Problem2:
When you resize the browser, the ratio changes every frame, and thus measure*ratio value becomes too low (to 0). Is there any algorithm I could apply to decrease the measure value slightly, but not drastically?
I have found the solution myself. I have added resize event listener with JavaScript and find the ratio with my own function and resize the canvas with it. I have added timeout not to see changes at the same time, but after some delay.
let timeOutFunctionId;
window.addEventListener("resize", function () {
if (!initVariables.subSet) {
r = findRatio(
initVariables.width,
initVariables.height,
initVariables.browserWidth,
initVariables.browserHeight
);
clearTimeout(timeOutFunctionId);
timeOutFunctionId = setTimeout(() => {
if (p5) {
if (true) {
initVariables.browserWidth = p5.windowWidth - 310;
initVariables.browserHeight = p5.windowHeight - 200;
} else {
}
p5.resizeCanvas(
initVariables.browserWidth,
initVariables.browserHeight
);
//calling custom class
}
}, 500);
}
});

Fabricjs responsive canvas items issues

i'm having some issues when the canvas width and height changes, i want the items to be in there same position..
i' using the latest version of fabricjs and using pdfjs library with it..
here's the code i'm using to try to fix it, but not working unfortunately:
function rescale_canvas_if_needed() {
var optimal_dimensions = [$(".canvasWrapper").outerWidth(), $(".canvasWrapper").outerHeight()];
var scaleFactorX = window.innerWidth / optimal_dimensions[0];
var scaleFactorY = window.innerHeight / optimal_dimensions[1];
if (scaleFactorX < scaleFactorY && scaleFactorX < 1) {
canvas.setWidth(optimal_dimensions[0] * scaleFactorX);
canvas.setHeight(optimal_dimensions[1] * scaleFactorX);
canvas.setZoom(scaleFactorX);
} else if (scaleFactorX > scaleFactorY && scaleFactorY < 1) {
canvas.setWidth(optimal_dimensions[0] * scaleFactorY);
canvas.setHeight(optimal_dimensions[1] * scaleFactorY);
canvas.setZoom(scaleFactorY);
} else {
canvas.setWidth(optimal_dimensions[0]);
canvas.setHeight(optimal_dimensions[1]);
canvas.setZoom(1);
}
canvas.calcOffset();
canvas.renderAll();
}
function handle_resize() {
$(".canvas-container").hide();
rescale_canvas_if_needed();
$(".canvas-container").show();
}
to test it: try adding an drawing on the book and click on the zoom in and out and see how the canvas items position is changing.
I am going to make the assumption that you've calculated the scale factor correctly - I'd either have to think really critically about the scaleFactorX and scaleFactorY idea or just play with code (of which I am too lazy to do at this moment), but you may only need to check for 1 of them for scaling:
function rescale_canvas_if_needed() {
var optimal_dimensions = [$(".canvasWrapper").outerWidth(), $(".canvasWrapper").outerHeight()];
// add a rectangle the size of the current canvas and center it
rect = new fabric.Rect({
left: 0,
top: 0,
width: canvas.width,
height: canvas.height,
angle: 0,
fill: 'rgba(255,255,255,0)',
transparentCorners: true
});
canvas.add(rect);
rect.center();
// get an active selection of all objects
canvas.discardActiveObject();
var sel = new fabric.ActiveSelection(canvas.getObjects(), {canvas: canvas});
canvas.setActiveObject(sel);
canvas.requestRenderAll();
// scale your canvas
var scaleFactorX = window.innerWidth / optimal_dimensions[0];
var scaleFactorY = window.innerHeight / optimal_dimensions[1];
if (scaleFactorX < scaleFactorY && scaleFactorX < 1) {
canvas.setWidth(optimal_dimensions[0] * scaleFactorX);
canvas.setHeight(optimal_dimensions[1] * scaleFactorX);
canvas.setZoom(scaleFactorX);
} else if (scaleFactorX > scaleFactorY && scaleFactorY < 1) {
canvas.setWidth(optimal_dimensions[0] * scaleFactorY);
canvas.setHeight(optimal_dimensions[1] * scaleFactorY);
canvas.setZoom(scaleFactorY);
} else {
canvas.setWidth(optimal_dimensions[0]);
canvas.setHeight(optimal_dimensions[1]);
canvas.setZoom(1);
}
canvas.calcOffset();
// scale the active selection to the new width
sel.scaleToWidth(canvas.width);
// center the selection
sel.center();
// deselect the active selection
canvas.discardActiveObject();
// delete the rectangle
canvas.remove(rect);
canvas.requestRenderAll();
}
function handle_resize() {
// I don't think you should have to hide or show the canvas-container
$(".canvas-container").hide();
rescale_canvas_if_needed();
$(".canvas-container").show();
}
This will discard any active selections on the page before resizing it - maybe thats how you will want it, but you could get the active selection before resizing everything, then once it is done with all the resizing, reselect that active selection.
Warning: I didn't test this, so it may have a bug, invalid syntax, or other problems within that nature.
Let me know if you can't past another hurtle, given this bit of code.

Fill window with divs. Div pixel height displayed incorrectly in google chrome. Width works

I want to fill the window size with divs. For a specified div size in px, the screen will be filled as much as it can be, leaving a remainder edge amount of px on the side and bottom. This remainder amount is then divided by the number of cells in the row (or column) and that is then added to the height (or width) of each cell in the row (or column).
For the width this works perfectly but when the same logic is applied to the height, it breaks. Both width and height work in firefox.
Screenshot: http://i.imgur.com/mpDCM0G.png
JSfiddle of making the divs: https://jsfiddle.net/xb82c4zt/
Live: http://conwaygameoflife.heroku.com/
var windowWidth = window.innerWidth;
var windowHeight = window.innerHeight;
var size = 100;
// Calculate the number of cells we can fit in the width
//and height (there will be extra space)
w = Math.floor(windowWidth / size);
h = Math.floor(windowHeight / size);
// Calculate the extra space
var widthDiff = windowWidth % size;
var heightDiff = windowHeight % size;
// Add the needed amount of height and width to each cell to fill the window
var widthSize = size + widthDiff / w;
var heightSize = size + heightDiff / h;
// Begin to alter the DOM
var parentDiv = document.createElement('div');
parentDiv.className = 'grid';
for(var y = 0; y < h; y++) {
for(var x = 0; x < w; x++) {
var cellDiv = document.createElement('div')
cellDiv.className = 'cellDiv'
cellDiv.style.height = heightSize + 'px';
cellDiv.style.width = widthSize + 'px';
parentDiv.appendChild(cellDiv)
}
}
document.body.appendChild(parentDiv)
In Chrome (and probably other browsers), height and width pixel values are truncated! See this stackoverflow answer with the related jsFiddle
Precentage values are truncated too, but not as severely. So, to solve this you can convert pixels to percentages as I did in this jsFiddle.
The main thing I added was:
var widthPercent = widthSize / windowWidth * 100;
var heightPercent = heightSize / windowHeight * 100;
Because we're using percentages now, the parent container must have width/height:
parentDiv.style.height = windowHeight + 'px';
parentDiv.style.width = windowWidth + 'px';
And changed the loop to:
for(var x = 0; x < w*h; x++) {
var cellDiv = document.createElement('div');
cellDiv.className = 'cellDiv';
cellDiv.style.height = heightPercent + '%';
cellDiv.style.width = widthPercent + '%';
parentDiv.appendChild(cellDiv)
}
Now this doesn't always work in chrome perfectly. However, it does make it perfect in some cases... basically depends on when (and how drastic) the truncation of percentages is.
After further reflection, it looks like percentages get resolved to fractional pixel values as well... which still get truncated in Chrome. So, let's make our math better, and figure out the biggest non-fractional pixel value we can use... it's actually really easy. See here
Basically, we just floor the values, then center the grid so that we can make it look nice.
edit: wasn't very happy with this answer, so screwed with it some more. Added a function that found the closest multiple of window size and made it so that it would prefer that number. Makes it work in most screen sizes, and has a fallback to the percentage method if it doesn't perfectly work. See here. However, because it relies on a recursive (naive) algorithm to find the closest multiple, it's really easy to screw your browser performance. Limiting to only 5-10 pixels of search space helps. The gist of it:
function closestMultiple(width, size, n, limit) {
if(n > limit) {
return {m: width/size, s:size};
}
if((width % (size+n)) == 0) {
return {m: width / (size+n), s: size+n};
} else if((width % (size-n)) == 0) {
return {m: width / (size-n), s: size-n};
}
return closestMultiple(width, size, n+1, limit);
}
It's very naive and ignores things like "an odd width will never be divisible by an even number"... so there's a ton of room for improvement. Check out this discussion and this discussion for more on this.

bounce check function for HTML5 canvas fails at corners

I have a HTML5 canvas that generates a bouncing box every time you click on it. The box array stores the x-value, y-value, x-velocity, and y-velocity of each box created. The box will travel in a random direction at first and will bounce of the sides of the canvas but if it hits a corner the box dissappears instead of bouncing back. EDIT: I answered my own question noticing that the soundY and soundX functions were causing the problem.
var box = new Array();
var width = window.innerWidth;
var height = window.innerHeight;
var field = document.getElementById('canvas');
field.width = width;
field.height = height;
field.ctx = field.getContext('2d');
field.ctx.strokeStyle = 'rgba(255,255,255,1)';
setInterval('redraw()', 200);
addEventListener('click', createBox, false);
function createBox(e) { // this box will always fail collision detection at the upper-left corner
box.push(100); // x-value is normally mouse position
box.push(100); // y-value is normally mouse position
box.push(-5); // x-speed is normally random
box.push(-5); // y-speed is normally random
}
function redraw() {
field.ctx.clearRect(0,0,width,height);
for(var i = 0; i < box.length; i+=4) {
if(box[i] < 0) { box[i+2] *= -1; soundY(box[i+1]); } // parameter of soundY is less than 0
else if(box[i] > width) { box[i+2] *= -1; soundY(box[i+1]); } // which is invalid and causes this to break
if(box[i+1] < 0) { box[i+3] *= -1; soundX(box[i]); }
else if(box[i+1] > height) { box[i+3] *= -1; soundX(box[i]); }
box[i] += box[i+2];
box[i+1] += box[i+3];
field.ctx.strokeRect(box[i], box[i+1], 4, 4);
}
}
function soundX(num) {
// play a sound file based on a number between 0 and width
}
function soundY(num) {
// play a sound file based on a number between 0 and height
}
The only way I could recreate the problem was by generating the box in one of the corners so that with the right x and y velocity the box was initially created outside the bounds of the canvas. When that happens, the inversion of the velocity isn't enough to bring the item back in bounds and so on the next frame the velocity is inverted again (and so on).
I think this might solve your problem:
var boxes = [];
var boxSize = 4;
var width = window.innerWidth;
var height = window.innerHeight;
var field = document.getElementById('canvas');
function redraw() {
field.ctx.clearRect(0, 0, width, height);
var box;
for (var i = 0; i < boxes.length; i++) {
box = boxes[i];
field.ctx.strokeRect(box.x, box.y, boxSize, boxSize);
if (box.x < 0) {
box.x = 0;
box.dx *= -1;
} else if (box.x > width - boxSize) {
box.x = width - boxSize;
box.dx *= -1;
}
if (box.y < 0) {
box.y = 0;
box.dy *= -1;
} else if (box.y > height - boxSize) {
box.y = height - boxSize;
box.dy *= -1;
}
box.x += box.dx;
box.y += box.dy;
}
}
field.width = width;
field.height = height;
field.ctx = field.getContext('2d');
field.ctx.strokeStyle = 'rgba(0,0,0,1)';
setInterval(redraw, 200);
addEventListener('click', createBox, false);
function createBox(e) {
boxes.push({
x: e.clientX - 10,
y: e.clientY - 10, // arbitrary offset to place the new box under the mouse
dx: Math.floor(Math.random() * 8 - boxSize),
dy: Math.floor(Math.random() * 8 - boxSize)
});
}
I fixed a few errors in your code and made some changes to make it a bit more readable (I hope). Most importantly, I extended your collision detection so that it resets the coordinates of the box to the bounds of your canvas should the velocity take it outside.
Created a jsfiddle which might be handy if further discussion is needed.
It was additional code (see edit) that I left out assuming it was unrelated to the issue, but removing the code solved the problem as it appears this use-case would cause an invalid input in this part of the code.

Categories