Question

I prepare code like masonry.

Rules are simple: - 4 columns always - each element has the same width

My script for positioning elements looks like below (onload):

var line = 0;
var row = 0;
var heights = [];

// Count elements on one line
$('img').each(function() {
    if ($(this).prev().length > 0) {
        if ($(this).position().top != $(this).prev().position().top) {
            return false;
        }

        line++;
    } else {
        line++;
    }
});

for (i = 0; i < $('img').length; i++) {
    if (i % line == 0 && i > 0) {
        row++;
    }

    // Set position in first row
    $('img:eq(' + i + ')').css({
        'position': 'absolute',
        'top': '0px',
        'left': $('img:first').outerWidth(true) * i
    });

    // Set position for next rows
    if (row > 0) {
        $('img:eq(' + i + ')').css({
            'top': parseFloat($('img:eq(' + (i - line) + ')').offset().top + $('img:eq(' + (i - line) + ')').outerHeight(true) - $('img:first').offset().top),
            'left': parseFloat($('img:eq(' + (i - line) + ')').css('left'))
        });
    }
}

And function to remove element on click

$('img').on('click', function() {
    $('img').css({
        'transition': 'all 3s'
    });

    $(this).remove();

    $(window).trigger('load');    
});

I would like to ask two questions:

  1. How to set height of div wrapper?
  2. Why when I remove an element (by click) do elements become not correctly positioned? I run method again by trigger.

All code on jsfiddle: http://jsfiddle.net/n8v3X/3/

Was it helpful?

Solution

(1) div wont get height, because its children are position absolute elements, height can be set via javascript.

>> Calculate total img height in each column

>> Find which column of images have greater height and set to wrap div

/* Logic to Calculate Height */
var wrap_height = 0;
var column_array = [0, 0, 0, 0];
for (i = 0; i <= row; i++) {
    var temp_height = 0;
    for (j = 0; j < line; j++) {
        //curr element in a row :  j+ ( i * line );
        var curr_height = $('img:eq(' + (j + (i * line)) + ')').outerHeight(true);

        column_array[j % line] += curr_height;

    }
}
for (i = 0; i < column_array.length; i++) {
    if (column_array[i] > wrap_height) {
        wrap_height = column_array[i];
    }
}

$('#wrap').css({
    height: wrap_height + 'px'
});

(b) Not properly positioned because,

the new position of prev element cant be calculated properly in loop.

$('img:eq(' + (i - line) + ')') >> top & left value get from below code, was before it get set to new position.

if (row > 0) {
            $('img:eq(' + i + ')').css({
                'top': parseFloat($('img:eq(' + (i - line) + ')').offset().top + $('img:eq(' + (i - line) + ')').outerHeight(true) - $('img:first').offset().top),
                'left': parseFloat($('img:eq(' + (i - line) + ')').css('left'))
            });
        }
    }

i used data attribute of html5 to fix this issue.

Fiddle: http://jsfiddle.net/aslancods/n8v3X/152/

OTHER TIPS

You need to calculate the height of the wrapper because absolutely-positioned elements are not in the layout. They have their own layout context.

Add up the height of the child elements that are in the same horizontal position and set the height of the parent wrapper from there.

Also, when an element is removed you need to re-calculate the position of the entire stack.

Here is my go: Fiddle Demo

Basically it's a different approach than the "position:absolute;" one. It provides a more flexible layout, because now you can easily do an action with one item, and all the items on it's column will move accordingly, and you can still call "spreadImages()" method if you want to rearrange the images on the page. The JS creates actual element for each column(float:left;), and inside it- the images are all display:block;ed.

CSS

div.column {
    width:100px;
    padding:10px 5px 0px;
    float:left;
    border:solid 1px red;
}
div.column img {
    display:block;
    margin-bottom:10px;
    width:100%;
}

JS

$(function () {
    var images,
        columns,
        numOfColumns = 4,
        container = $('#container');

    // create rows
    var createColumns = function() {
        for (var i = 0; i < numOfColumns; i++) {
            $('<div />').attr('id', 'row_' + i)
                        .addClass('column')
                        .appendTo(container);
        }
        columns = $('.column', container);
    };

    // empty rows
    var resetColumns = function(){ columns.html(''); };

    // returns the shortest column at the moment
    var getShortestColumn = function() {
        var shortestColumn;
        columns.each(function () {
            if (!shortestColumn) {
                shortestColumn = $(this);
            } else if ($(this).outerHeight() < shortestColumn.outerHeight()) shortestColumn = $(this);
        });
        return shortestColumn;
    };

    // spread images between rows
    var spreadImages = function() {
        images = $('img');
        resetColumns();
        images.each(function () {
            var img = $(this);
            var height = img.outerHeight();
            var shortestColumn = getShortestColumn();
            shortestColumn.append(img);
        });
    };

    var init = function(){
        createColumns();
        spreadImages();
    };
    init();
    container.on('click', 'img', function(){
        var img = $(this);
        img.animate({height:0, opacity:0}, function(){
            img.remove();
            spreadImages();
        });
    });
});
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top