The waterfall layout organizes content blocks in columns of varying heights but equal width. This design is efficient for presenting heterogeneous items like images with captions, as it uses vertical space dynamically without horizontal gaps.
The core container (#main) is styled for central alignment and relative posisioning, which is necessary for absolute positioning of child elements later.
#main {
position: relative;
text-align: center;
margin: 0 auto;
}
Margin shorthand margin: 0 auto; handles top/bottom and left/right spacing, centering the block horizontally within its parent. Calculating the number of columns per row is essential. This is done by dividing the viewport width (minus any gutters) by the fixed width of a single content block.
Here is the JavaScript logic to arrange the blocks:
function arrangeWaterfall() {
const itemSelector = '#main > .item';
const contentBlocks = $(itemSelector);
const blockWidth = contentBlocks.first().outerWidth(true);
const totalGutter = 30;
const columnCount = Math.floor(($(window).width() - totalGutter) / blockWidth);
$('#main').width(blockWidth * columnCount).css('margin', '0 auto');
let columnHeights = [];
contentBlocks.each(function(idx, block) {
const blockHeight = $(this).outerHeight(true);
if (idx < columnCount) {
columnHeights[idx] = blockHeight;
} else {
const currentMinHeight = Math.min(...columnHeights);
const shortestColumnIndex = columnHeights.indexOf(currentMinHeight);
$(this).css({
'position': 'absolute',
'top': currentMinHeight + 'px',
'left': (shortestColumnIndex * blockWidth) + 'px'
});
columnHeights[shortestColumnIndex] += blockHeight;
}
});
}
The outerWidth(true) and outerHeight(true) methods include margins in the calculation. An array columnHeights tracks the cumulative height of each column. For the first row (index less than columnCount), heights are stored directly. Subsequent blocks are placed in the column with the current minimum height, and that colum's total height is updated.
To load more content as the user scrolls, an event listener checks if the scroll position is near the bottom of the existing layout.
$(window).on('scroll', function() {
if (shouldLoadMore()) {
// Function to append new data blocks to #main
appendNewContent(dataSet);
arrangeWaterfall();
}
});
function shouldLoadMore() {
const lastItem = $('#main > .item').last();
const lastItemTriggerPoint = lastItem.offset().top + (lastItem.outerHeight() / 2);
const currentScroll = $(window).scrollTop();
const viewportHeight = $(window).height();
return (lastItemTriggerPoint < currentScroll + viewportHeight);
}
The trigger point is set to the vertical midpoint of the last block. When the sum of the scroll position and viewport height exceeds this point, new content is fetched and the layout functon is called again to reposition all items.