Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Three Ways to Implement a Waterfall Layout

Tech 1

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.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.