tags, keep them
* around this code.
*/
(function () {
var LIST_SELECTOR = 'ul.products';
var TITLE_SELECTOR = '.woocommerce-loop-product__title';
var TOLERANCE = 6; // Cards whose tops are within 6px count as one row.
var rafId = 0;
function equalize() {
rafId = 0;
var titles = [];
document.querySelectorAll(LIST_SELECTOR).forEach(function (list) {
if (list.offsetParent === null) return; // Hidden grid, e.g. in a closed tab.
list.querySelectorAll(':scope > li.product ' + TITLE_SELECTOR).forEach(function (title) {
titles.push(title);
});
});
if (!titles.length) return;
// 1. Clear the previous heights (writes only).
titles.forEach(function (title) {
title.style.minHeight = '';
});
// 2. Measure every title in one pass (reads only, so the page is laid out once).
var rows = [];
titles.forEach(function (title) {
var card = title.closest('li.product');
var top = Math.round(card.getBoundingClientRect().top);
var height = Math.ceil(title.getBoundingClientRect().height);
var row = null;
for (var i = 0; i < rows.length; i++) {
if (rows[i].list === card.parentNode && Math.abs(rows[i].top - top) <= TOLERANCE) {
row = rows[i];
break;
}
}
if (!row) {
row = { list: card.parentNode, top: top, max: 0, items: [] };
rows.push(row);
}
row.items.push(title);
if (height > row.max) row.max = height;
});
// 3. Apply the tallest height in each row (writes only).
rows.forEach(function (row) {
if (row.items.length < 2) return;
row.items.forEach(function (title) {
title.style.minHeight = row.max + 'px';
});
});
}
function schedule() {
if (!rafId) rafId = requestAnimationFrame(equalize);
}
function init() {
if (!document.querySelector(LIST_SELECTOR)) return; // No product grid on this page.
schedule();
window.addEventListener('load', schedule);
if (document.fonts) {
if (document.fonts.ready) document.fonts.ready.then(schedule);
if (document.fonts.addEventListener) document.fonts.addEventListener('loadingdone', schedule);
}
// Re-run when a grid changes width, not when its height changes.
var watched = new WeakSet();
var widths = new WeakMap();
var resizeObserver = null;
if ('ResizeObserver' in window) {
resizeObserver = new ResizeObserver(function (entries) {
var changed = false;
entries.forEach(function (entry) {
var width = Math.round(entry.contentRect.width);
if (widths.get(entry.target) !== width) {
widths.set(entry.target, width);
changed = true;
}
});
if (changed) schedule();
});
} else {
window.addEventListener('resize', schedule);
}
function watchLists() {
if (!resizeObserver) return;
document.querySelectorAll(LIST_SELECTOR).forEach(function (list) {
if (!watched.has(list)) {
watched.add(list);
resizeObserver.observe(list);
}
});
}
watchLists();
// Re-run only when products are added or removed (Load More, filters).
function touchesProducts(node) {
return node.nodeType === 1 && (node.matches('li.product, ' + LIST_SELECTOR) || !!node.querySelector('li.product, ' + LIST_SELECTOR));
}
var mutationObserver = new MutationObserver(function (records) {
for (var i = 0; i < records.length; i++) {
var lists = [records[i].addedNodes, records[i].removedNodes];
for (var k = 0; k < 2; k++) {
for (var j = 0; j < lists[k].length; j++) {
if (touchesProducts(lists[k][j])) {
watchLists();
schedule();
return;
}
}
}
}
});
var roots = document.querySelectorAll('.woocommerce, .elementor-widget-wc-archive-products, .elementor-widget-woocommerce-products');
(roots.length ? roots : [document.body]).forEach(function (root) {
mutationObserver.observe(root, { childList: true, subtree: true });
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();