Javascript scale images to fit container and to equal height - javascript

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.

Related

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

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.

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.

Column height via Javascript

I am using
columns: 15em;
to automagically format some images on a page. I would like to put filler images in the empty bottoms of the columns, shorter then the longest column. Can I find out what height these images would need to be?
Also sometimes the columns don't format the images optimally. Am I missing something here?
You can play around with my code here:
http://jsfiddle.net/ygc6r0as/
This should give you a start. It passes through the images and determines which column the image is currently placed in using simple arithmetic, as well as the total height of each column.
var images = document.getElementsByTagName('img'),
cols = [],
colHeights = [],
baseOffset = images[0].offsetLeft;
for (var i=0; i < images.length; i++) {
var img = images[i];
var col = Math.round( (img.offsetLeft - baseOffset) / img.width );
if (!cols[col]) {
cols[col] = [];
colHeights[col] = 0;
}
cols[col].push( img );
var bottom = img.offsetTop + img.offsetHeight;
if (bottom > colHeights[col])
colHeights[col] = bottom;
}
console.log(cols, colHeights);

Display number of divs according to the window size

For example the below image is at initial window position..
When I will decrease the window size then the number of divs should change accordingly.
Edit : Each map represents a Div.
Get sizes of maps:
var maps = document.getElementsByClassName('.map');
for(var i = 0; i < maps.length; i++){
var map = maps[i];
var rect = map.getClientRects()[0];
map.left = rect.left;
map.top = rect.top;
map.width = rect.width;
map.height = rect.height;
}
On screen resize event, where you will check:
for(var i = 0; i < maps.length; i++){
if(maps[i].left + maps[i].width > screen.width ||
maps[i].top + maps[i].height> screen.height)
maps[i].style.display = 'none';
else
maps[i].style.display = 'block';
}
I have not tested, hope it works
With jQuery, use the .resize method to check whether the window has been resized, then calculate how many divs you want to display (which is the window size divided by your div size). To get window height and width:
var h=$(window).height(); // returns height of browser viewport
var w=$(window).width(); // returns width of browser viewport
You can use .hide and .show to nicely reveal the exact number of divs.

Drawing zoomable audio waveform timeline in Javascript

I have raw 44,1 kHz audio data from a song as Javascript array and I'd like to create a zoomable timeline out of it.
Example timeline from Audacity:
Since there are millions of timepoints normal Javascript graphics libraries probably don't cut it: I think, not sure, that normal graph libraries will die on this many timepoints. But does there exist already libraries for this sort of visualization for JS? Canvas, webGL, SVG all are acceptable solutions.
A solution preferably with zoom and pan.
Note that this happens strictly on client side and server-side solutions are not accetable.
I've looked into this same problem pretty extensively. To the best of my knowledge, the only existing project that does close to what you want is wavesurfer.js. I haven't used it, but the screenshots and the description sound promising.
See also this question.
Best of luck.
You cannot simply take the the waveform data and render all data points, this is terribly inefficient.
Variable explanation:
width: Draw area width in pixels, max is screen width
height: Same as width but then height of draw area
spp: Samples per pixel, this is your zoom level
resolution: Number of samples to take per pixel sample range, tweak for performance vs accuracy.
scroll: You will need virtual scrolling for performance, this is the scroll position in px
data: The raw audio data array, probably several million samples long
drawData: The reduced audio data used to draw
You are going to have to only take the samples that are in the viewport from the audio data and reduce those. Commenly this results in a data set that is 2 * width, you use this data set to render the image.
To zoom out increase spp, to zoom in decrease it. Changing scroll value pans it.
The following code has O(RN) complexity where N is width and R is resolution. Maximum accuracy is at spp <= resolution.
The code will look something like this, this gets the peak values, you could do rms or average as well.
let reduceAudioPeak = function(data, spp, scroll, width, resolution) {
let drawData = new Array(width);
let startSample = scroll * spp;
let skip = Math.ceil(spp / resolution);
// For each pixel in draw area
for (let i = 0; i < width; i++) {
let min = 0; // minimum value in sample range
let max = 0; // maximum value in sample range
let pixelStartSample = startSample + (i * spp);
// Iterate over the sample range for this pixel (spp)
// and find the min and max values.
for(let j = 0; j < spp; j += skip) {
const index = pixelStartSample + j;
if(index < data.length) {
let val = data[index];
if (val > max) {
max = val;
} else if (val < min) {
min = val;
}
}
}
drawData[i] = [min, max];
}
return drawData;
}
With this data you can draw it like this, you could use lines, svg etc:
let drawWaveform = function(canvas, drawData, width, height) {
let ctx = canvas.getContext('2d');
let drawHeight = height / 2;
// clear canvas incase there is already something drawn
ctx.clearRect(0, 0, width, height);
for(let i = 0; i < width; i++) {
// transform data points to pixel height and move to centre
let minPixel = drawData[i][0] * drawHeigth + drawHeight;
let maxPixel = drawData[i][1] * drawHeight + drawHeight;
let pixelHeight = maxPixel - minPixel;
ctx.fillRect(i, minPixel, 1, pixelHeight);
}
}
I have used RaphaelJS for SVG rendering in the browser at it has performed very well. It is what I would go for. Hopefully SVG will be up to the task.

Categories