Three Ways to Implement a Waterfall Layout
This article demonstrates three methods to implement a waterfall (masonry) layout: using plain JavaScript, jQuery, and CSS.
Method 1: Using JavaScript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Waterfall Layout</title>
<style>
* { padding: 0; margin: 0; }
.clearfix::after,
.clearfix::before {
content: " ";
display: table;
}
.clearfix::after {
clear: both;
}
.main {
position: relative;
-webkit-column-width: 210px;
-moz-column-width: 210px;
-webkit-column-gap: 5px;
-moz-column-gap: 5px;
}
.box {
float: left;
padding: 15px 0 0 15px;
}
.box .pic {
width: 180px;
height: auto;
padding: 10px;
border-radius: 5px;
box-shadow: 0 0 5px #cccccc;
border: 1px solid #cccccc;
}
.box .pic img {
display: block;
width: 100%;
}
</style>
</head>
<body>
<div class="main clearfix" id="main">
<div class="box"><div class="pic"><img src="./images/0.jpg"></div></div>
<div class="box"><div class="pic"><img src="./images/1.jpg"></div></div>
<!-- ... more boxes ... -->
</div>
<script>
window.onload = function() {
createMasonry('main', 'box');
// Mock JSON data
var mockData = { 'data': [
{ 'src': '30.jpg' }, { 'src': '31.jpg' }, { 'src': '32.jpg' }, { 'src': '33.jpg' },
{ 'src': '34.jpg' }, { 'src': '35.jpg' }, { 'src': '36.jpg' }, { 'src': '37.jpg' },
{ 'src': '38.jpg' }, { 'src': '39.jpg' }, { 'src': '40.jpg' }, { 'src': '41.jpg' },
{ 'src': '42.jpg' }, { 'src': '43.jpg' }, { 'src': '44.jpg' }, { 'src': '45.jpg' }
]};
// Listen for scroll
window.onscroll = function() {
var isPosting = false;
if (checkScrollReach('main', 'box') && !isPosting) {
var parent = document.getElementById('main');
for (var i = 0; i < mockData.data.length; i++) {
isPosting = true;
var box = document.createElement('div');
box.className = 'box';
box.innerHTML = '<div class="pic"><img src="./images/' + mockData.data[i].src + '"></div>';
parent.appendChild(box);
}
isPosting = false;
createMasonry('main', 'box');
}
};
};
function createMasonry(parentId, childClass) {
var parent = document.getElementById(parentId);
var boxes = parent.getElementsByClassName(childClass);
var boxWidth = boxes[0].offsetWidth;
var cols = Math.floor(document.documentElement.clientWidth / boxWidth);
parent.style.cssText = 'width:' + (boxWidth * cols) + 'px; margin:0 auto;';
var heights = [];
for (var i = 0; i < boxes.length; i++) {
if (i < cols) {
heights[i] = boxes[i].offsetHeight;
} else {
var minHeight = Math.min.apply(null, heights);
var minIndex = getMinIndex(heights, minHeight);
boxes[i].style.cssText = 'position:absolute; top:' + minHeight + 'px; left:' + boxes[minIndex].offsetLeft + 'px;';
heights[minIndex] += boxes[i].offsetHeight;
}
}
}
function getMinIndex(arr, val) {
for (var i in arr) {
if (arr[i] == val) return i;
}
}
function checkScrollReach(parentId, childClass) {
var parent = document.getElementById(parentId);
var boxes = parent.getElementsByClassName(childClass);
var lastBox = boxes[boxes.length - 1];
var lastBoxTrigger = lastBox.offsetTop + lastBox.offsetHeight / 2;
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
var viewHeight = document.documentElement.clientHeight || document.body.clientHeight;
return lastBoxTrigger < scrollTop + viewHeight;
}
</script>
</body>
</html>
Method 2: Using jQuery
$(window).on('load', function() {
createMasonry('main', 'box');
var mockData = { 'data': [
{ 'src': '30.jpg' }, { 'src': '31.jpg' }, { 'src': '32.jpg' }, { 'src': '33.jpg' },
{ 'src': '34.jpg' }, { 'src': '35.jpg' }, { 'src': '36.jpg' }, { 'src': '37.jpg' },
{ 'src': '38.jpg' }, { 'src': '39.jpg' }, { 'src': '40.jpg' }, { 'src': '41.jpg' },
{ 'src': '42.jpg' }, { 'src': '43.jpg' }, { 'src': '44.jpg' }, { 'src': '45.jpg' }
]};
window.onscroll = function() {
var isPosting = false;
if (checkScrollReach('main', 'box') && !isPosting) {
isPosting = true;
$.each(mockData.data, function(index, item) {
var $box = $('<div class="box"><div class="pic"><img src="./images/' + item.src + '"></div></div>');
$('#main').append($box);
createMasonry('main', 'box');
});
isPosting = false;
}
};
});
function createMasonry(parentId, childClass) {
var $parent = $('#' + parentId);
var $boxes = $parent.find('.' + childClass);
var boxWidth = $boxes.eq(0).outerWidth(true);
var cols = Math.floor($(window).width() / boxWidth);
$parent.width(boxWidth * cols).css('margin', '0 auto');
var heights = [];
$boxes.each(function(index) {
if (index < cols) {
heights[index] = $(this).outerHeight(true);
} else {
var minHeight = Math.min.apply(null, heights);
var minIndex = $.inArray(minHeight, heights);
$(this).css({
'position': 'absolute',
'top': minHeight,
'left': $boxes.eq(minIndex).position().left
});
heights[minIndex] += $(this).outerHeight(true);
}
});
}
function checkScrollReach(parentId, childClass) {
var $lastBox = $('#' + parentId).find('.' + childClass).last();
var triggerPoint = $lastBox.offset().top + $lastBox.outerHeight() / 2;
var scrollTop = $(window).scrollTop();
var viewHeight = $(window).height();
return triggerPoint < scrollTop + viewHeight;
}
Method 3: Using CSS
.clearfix::after,
.clearfix::before {
content: " ";
display: table;
}
.clearfix::after {
clear: both;
}
.main {
position: relative;
-webkit-column-width: 210px;
-moz-column-width: 210px;
-webkit-column-gap: 5px;
-moz-column-gap: 5px;
}
.box {
float: left;
padding: 15px 0 0 15px;
}
.box .pic {
width: 180px;
height: auto;
padding: 10px;
border-radius: 5px;
box-shadow: 0 0 5px #cccccc;
border: 1px solid #cccccc;
}
.box .pic img {
display: block;
width: 100%;
}
Comparison
- JavaScript / jQuery approach: Requires calculation: column count = window width / image width; images are positioned based on the cumulative height of each column. The layout arranges images hroizontally based on computed positions, resulting in a more organized presentation.
- CSS approach: No calculation needed; browser automatically adjusts. Only need to set column width, offering better performance. However, column width changes with the browser window size, wich may degrade user experience. Image are arranged vertically in order, disrupting the visual sequence. Image loading still depends on JavaScript or jQuery.