Breaking group of items into rows then ensure consistent height of each row element I'm working on a product category page where the product images have inconsistent heights so I've been asked to improve the page appearance without changing the images.

The page contains x number of products in a 3 column grid.

I decided to set the height of the image wrapper to the height of the image with the maximum height in each row, then use flex to vertically center the images.

How could I improve my JavaScript?



/**
 *  Ensure all rows appear the same height even if they contain products with
 *  small images.
 */

const imgWrappers = Array.from(document.querySelectorAll('.summary-image-wrapper'));
const rows = [];

// 3 Products per row - First group the image wrappers into groups of 3
while (imgWrappers.length !== 0) {
    rows.push({
        elements: imgWrappers.splice(0, 3),
    });
}

// Then establish which element is the tallest in it's group
rows.forEach((row) =&gt; {
    let height = 0;

    row.elements.forEach((el) =&gt; {
        const currHeight = el.firstElementChild.getAttribute('height');
        height = currHeight &gt; height ? currHeight : height;
    });

    row.height = height;

    // Set the height of each element to that of the tallest element in the row
    row.elements.forEach((el) =&gt; {
        el.style.height = `${row.height}px`;
    });
});
&lt;div class="container"&gt;
    &lt;div class="row"&gt;
        &lt;div class="col-6 col-md-4"&gt;
         &lt;div class="summary-product"&gt;
             &lt;a href="http://examplelink.com/" class="woocommerce-LoopProduct-link woocommerce-loop-product__link"&gt;
                    &lt;div class="summary-image-wrapper" style="height: 413px;"&gt;
                        &lt;img width="300" height="413" src="http://via.placeholder.com/300x413" class="attachment-woocommerce_thumbnail size-woocommerce_thumbnail wp-post-image" alt="alt ttext" srcset="http://via.placeholder.com/300x413 300w, http://via.placeholder.com/218x300 218w, http://via.placeholder.com/350x482 350w, http://via.placeholder.com/581x800 581w" sizes="(max-width: 300px) 100vw, 300px"&gt;
                    &lt;/div&gt;
                    &lt;h2 class="woocommerce-loop-product__title summary-product-title"&gt;Example title&lt;/h2&gt;
                 &lt;span class="price summary-product-price"&gt;POA&lt;/span&gt;
                &lt;/a&gt;
         &lt;/div&gt;
            &lt;!-- Further .summary-products have been omitted for brevity--&gt; 
        &lt;/div&gt;
     &lt;/div&gt;
&lt;/div&gt;




EDIT:
I discovered an error within my original code due to string comparison. Take the following row of 3 images and their height:

Image a: 365

Image b: 91

Image c: 401

Given the above images using the code originally given 'image b' will be considered the largest. This is because el.firstElementChild.getAttribute('height') returns a string, when comparing strings javascript does it "alphabetically". This means that js looks at the first character of the string then the second and so on. When comparing two numberic strings if a string starts with a 9 it will always be the largest!

'10000000000' &gt; '9' //false
'89999999999' &gt; '9' //false
'91' &gt; '9' //true


To ensure the original code works the value stored in currHeight needs to be converted to a number e.g currHeight = Number.parseInt(el.firstElementChild.getAttribute('height')); before comparison.
Keep it simple
Your algorithm is too complex, with too many iterations and uses too much memory. It can be simplified using only a single outer iteration to get a row, and the row's max height. Then an inner loop to set the new height values. (see rewrite)
Some points

document.querySelectorAll returns an iterable object. There is no need to convert it to an array. You can use for of or any of the iteration features on it. EG document.querySelectorAll(&quot;query string&quot;).forEach( and [...document.querySelectorAll(&quot;query string&quot;)]

You only need to use .getAttribute when the attribute is not part of the DOM. const height = element.height is more concise.

You can use the Math function max  to get the maximum of 2 or more values at a time. maxHeight = Math.max(maxHeight, element.height)

Don't pollute the global scope. Use a self invoking function to encapsulate your variables.  ;(()=&gt;{ /* your code in here */ })();


Rewrite
;(() =&gt; {
    &quot;use strict&quot;;
    const itemsPerRow = 3;
    const query = &quot;.summary-image-wrapper&quot;;
    const minHeight = 100; // Maybe a min height just in case

    const row = [];
    var maxHeight = minHeight;

    document.querySelectorAll(query)
        .forEach((imgWrap, i, imgWrappers) =&gt; {
            row.push(imgWrap);
            maxHeight = Math.max(imgWrap.firstElementChild.height , maxHeight);

            if (row.length === itemsPerRow || i === imgWrappers.length - 1) {
                row.forEach(imgWrap =&gt; imgWrap.style.height = `${maxHeight}px`);
                maxHeight = minHeight;
                row.length = 0;
            }
        });
})();