The problem:

Create a function such that each row on a page has a static number of images (let’s say 3) of unknown dimensions where the images are resized such that the widths are equal to a dynamically defined width and the heights of each image are equal to all other images in the row while maintaining the original aspect ratio of each image.

Oh and we want it to scale for mobile too.

The solution:

Let’s start with an HTML structure:

<div class="wrapper">
    <img src="/image1.jpg" class="gallery" />
    <img src="/image2.jpg" class="gallery" />
    <img src="/image3.jpg" class="gallery" />
    <img src="/image4.jpg" class="gallery" />
    <img src="/image5.jpg" class="gallery" />
    <img src="/image6.jpg" class="gallery" />
    <img src="/image7.jpg" class="gallery" />
    <img src="/image8.jpg" class="gallery" />
    <img src="/image9.jpg" class="gallery" />
    <img src="/image10.jpg" class="gallery" />
    <img src="/image11.jpg" class="gallery" />
    <img src="/image12.jpg" class="gallery" />
</div>

Next let’s start our function, we’ll need to pass in the number of columns or images per row that are desired. This needs to be a variable because we’ll be running this script when the browser is resized based on the viewport width.

function imageWrapper(columns) {
}

The dynamically defined width of the wrapping element is pretty straightforward:

function imageWrapper(columns) {
    $('.wrapper').width();
}

Next we need to place our galleries into a variable and count how many there are. This allows us to iterate through them one by one and resize based on the columns parameter

function imageWrapper(columns) {
    $('.wrapper').width();
    var galleries = jQuery('.gallery');
    var galleriesLen = galleries.length;
}

Now we start our for loop. Remember: for (starting-expression; condition-to-test-against; increment-or-decrement-expression){}.

Here we’re starting with i at 0 and as long as i is less than the total number of images or galleries we have, it will run the code inside the for loop and then increase i by whatever the columns parameter is.

function imageWrapper(columns) {
    $('.wrapper').width();
    var galleries = jQuery('.gallery');
    var galleriesLen = galleries.length;

    for (var i = 0;i < galleriesLen;i+=columns){
    }
}

Next we’ll:

  1. Define a variable called aspectRatio as 0.
  2. Filter each gallery so that we group the galleries into sections of ‘x’ (defined as columns
  3. For each gallery or image in each section we add the aspect ratio of the given image by dividing it’s width by it’s height.
  4. Find the height that we’re aiming for, for each row of images or galleries.
function imageWrapper(columns) {
    $('.wrapper').width();
    var galleries = jQuery('.gallery');
    var galleriesLen = galleries.length;

    for (var i = 0;i < galleriesLen;i+=columns){
        var aspectRatios = 0;
        var galleriesFilter = galleries.filter(':eq('+i+'),:lt('+(i+columns)+'):gt('+i+')');

	galleriesFilter.each(function() {
	    aspectRatios = aspectRatios + ( jQuery(this).width() / jQuery(this).height() );
	});

	var height = (Math.floor(outerWidth / aspectRatios));
    }
}

Finally, for each element in each row, we set the width and height dynamically now that we know the height and the aspect ratio. I round here to avoid sub-pixel issues in various browsers.

function imageWrapper(columns) {
    $('.wrapper').width();
    var galleries = jQuery('.gallery');
    var galleriesLen = galleries.length;

    for (var i = 0;i < galleriesLen;i+=columns){
        var aspectRatios = 0;
        var galleriesFilter = galleries.filter(':eq('+i+'),:lt('+(i+columns)+'):gt('+i+')');

	galleriesFilter.each(function() {
	    aspectRatios = aspectRatios + ( jQuery(this).width() / jQuery(this).height() );
	});

	var height = (Math.floor(outerWidth / aspectRatios));

        galleriesFilter.each(function() {
	    jQuery(this).width( Math.round(height * (jQuery(this).width() / jQuery(this).height()) ) + "px");
	});

	galleriesFilter.height(height + "px");
    }
}

One of the trickiest things here ends up being the geometry. Mathematically, the problem we’re trying to solve is something like the following:

w1 = width 1;
h1 = height 1;
w2 = width 2;
h2 = height 2;
w3 = width 3;
h3 = height 3;
Ow = Overall Width;

w1 + w2 + w3 = Ow
h1 = h2 = h3

The only things we know for sure are the overall width, and the aspect ratio of each image individually. By taking the aspect ratio of each image in it’s decimal form (eg 3.1) and adding it to the aspect ratios of the other images in any given row, we can then take the overall width and divide it by the total aspect ratio to get the height.

Once we have the height we can find the width of each image based on the aspect ratio and voila!

Now that we have the function built, it’s relatively easy to change the number of elements in the given row by doing something like the following:

$(window).resize(function() {
    if($(window).width() > 1300) {
        imageWrapper(5);
    } else if($(window).width() > 900) {
        imageWrapper(3);
    } else if($(window).width() > 768) {
        imageWrapper(1);
    } else 
});

You can see a working example of this on the Non-Fiction Photography website.