Bootstrap + Masonry добавляет пустые divs, когда сетка имеет пустые пространства внизу

Я использую masonry + bootstrap и замечаю, что когда у меня есть решетка из 3 столбцов, у меня есть 10 элементов, она будет отображать сетку 3x4 с двумя пустыми местами внизу. Как я могу автоматически добавить 2 пустых divs внизу, чтобы заполнить его и не иметь эти пробелы? Итак, общий div станет 12, в котором 2 divs просто пусты?

Это не должно быть привязано к 3-столбцу, но должно динамически добавлять пустые divs каждый раз, когда обнаруживается, что существует N количество пустых div, которые могут быть заполнены. Должен быть применим при загрузке и изменении размера.

Не будет проблем с размером .item, так как все они будут иметь одинаковую ширину и высоту (box/square type)

Я создал jsFiddle, который теперь может добавлять наполнители в пустые пространства в последней строке. Это также работает с изменением размера, используя событие layoutComplete. Но проблема в том, что всякий раз, когда я изменяю размер, он продолжает добавлять новые наполнители.

Попробуйте изменить размер до разных размеров, и вы заметите, что он продолжает добавлять наполнители.

В случае, здесь также код.

HTML

<input type="hidden" name="hfTotalGridItems" id="hfTotalGridItems" value="10" />
<div class="grid">
    <div class="item">
        <div>lorem</div>
    </div>
    <div class="item">
        <div>lorem</div>
    </div>
    <div class="item">
        <div>lorem</div>
    </div>
    <div class="item">
        <div>lorem</div>
    </div>
    <div class="item">
        <div>lorem</div>
    </div>
    <div class="item">
        <div>lorem</div>
    </div>
    <div class="item">
        <div>lorem</div>
    </div>
    <div class="item">
        <div>lorem</div>
    </div>
    <div class="item">
        <div>lorem</div>
    </div>
    <div class="item">
        <div>lorem</div>
    </div>
</div>
<div class="result"></div>

CSS

.grid {
    margin: 0 auto;
}
.item {
    margin-bottom: 20px;
    border: 1px solid red;
    height: 80px;
    width: 80px;        
}
.filler {
    background-color: #999;
    border: 1px solid blue;
}

JQuery

$(function () {
    function calculateRows() {
        var lisInRow = 0;
        var $item = $('.grid .item');
        var $grid = $('.grid');
        var itemWidth = $('.grid .item').width();
        var itemHeight = $('.grid .item').height();

        $item.each(function () {
            if ($(this).prev().length > 0) {
                if ($(this).position().top != $(this).prev().position().top) return false;
                lisInRow++;
            } else {
                lisInRow++;
            }
        });

        var lisInLastRow = $item.length % lisInRow;
        if (lisInLastRow == 0) lisInLastRow = lisInRow;

        $('.result').html('No: of lis in a row = ' + lisInRow + '<br>' + 'No: of lis in last row = ' + lisInLastRow);

        if (lisInLastRow < lisInRow) {
            var $clonedItem = $('.grid .item:last-child').clone().empty().css({
                width: itemWidth,
                height: itemHeight
            }).addClass('filler');
            $grid.append($clonedItem).masonry('appended', $clonedItem);

        } else {
            if (newTotal > $('#hfTotalGridItems').val()) {
                $grid.masonry('remove', $('.grid .item.filler'));
                $grid.masonry();
            }
        }
    }

    var $grid = $('.grid');

    $grid.masonry({
        itemSelector: '.item',
        isFitWidth: true,
        gutter: 20
    });

    $grid.masonry('on', 'layoutComplete', function (event) {
        calculateRows(event.length);
    });

    $grid.masonry();
});

Ответ 1

Есть две вещи, которые вам нужно проверить

  • Если количество исходных полей равномерно делится на количество ящиков в первой строке (originalBoxs % lisInRow === 0).
  • Если количество ящиков больше, то максимально допустимое количество ящиков. Вы можете рассчитать максимально допустимые поля ниже

    var totalAllowed = lisInRow;
    while (totalAllowed < originalBoxs) {
       totalAllowed += lisInRow;
    }
    

Если это правда, вы должны удалить все лишние поля. В противном случае добавьте в поля наполнителя. Ниже приведен обновленный jsFiddle

Я добавил обновленный код jQuery ниже

$(function () {

    // remember the original box lenth. 
    var $item = $('.grid .item');
    var originalBoxs = $item.length;

    function calculateRows() {

        var lisInRow = 0;
        var $item = $('.grid .item');
        var $grid = $('.grid');
        var itemWidth = $('.grid .item').width();
        var itemHeight = $('.grid .item').height();

        // calculate how many boxes are in the first row. 
        $item.each(function () {
            if ($(this).prev().length > 0) {
                if ($(this).position().top != $(this).prev().position().top) return false;
                lisInRow++;
            } else {
                lisInRow++;
            }
        });

        // calculate how many boxes are in the last row. 
        var lisInLastRow = $item.length % lisInRow;

        $('.result').html('No: of lis in a row = ' + lisInRow + '<br>' + 'No: of lis in last row = ' + lisInLastRow);

        // the total allowed boxes on the page. 
        var totalAllowed = lisInRow;
        while (totalAllowed < originalBoxs) {
            totalAllowed += lisInRow;
        }

        if (($item.length > originalBoxs && originalBoxs % lisInRow === 0) || ($item.length > totalAllowed)) {
            // if the number of original boxes evenly divides into the number of boxes in a row.
            // or the number of boxes on the page is past the max allowed. 
            // remove any filler boxes. 
            var boxesToDelete = $('.grid .item.filler');
            var deleteBoxes = $item.length - totalAllowed;
            for (var i = 0; i < deleteBoxes; i++) {
                // delete unnesecary boxes. 
                $grid.masonry('remove', boxesToDelete[boxesToDelete.length - i - 1]);
            }
        } else if (lisInLastRow !== 0) {
            // if the last row does not equal 0 and the number of boxes is less then the original + the first row
            // then fill it in with new boxes. 
            var $clonedItem = $('.grid .item:last-child').clone().empty().css({
                width: itemWidth,
                height: itemHeight
            }).addClass('filler');
            $grid.append($clonedItem).masonry('appended', $clonedItem);    
        }

    }

    var $grid = $('.grid');

    $grid.masonry({
        itemSelector: '.item',
        isFitWidth: true,
        gutter: 20
    });

    $grid.masonry('on', 'layoutComplete', function (event) {
        calculateRows(event.length);
    });

    $grid.masonry();
});

Ответ 2

Вот немного другая версия. Пожалуйста проверите этот традиционный JSFiddle для тестов.

Короче говоря, я взял способ неинтрузивной модификации для масонства для достижения ожидаемого поведения наиболее оптимальным способом.

$(function() {

  var fillerHtml = '<div></div>',
    fillerClassName = 'filler',
    initialItemCount = null;

  function toggleFillers() {
    var masonry = $grid.data('masonry'),
      currentItemCount = masonry.items.length,
      items = masonry.items,
      cols = masonry.cols,
      expectedItemCount = 0,
      lastRowItemCount = 0,
      lastRowFillerCount = 0,
      fillersToRemove = [],
      fillersToAddCount = 0,
      fillersToAdd = [];

    if (initialItemCount === null) {
      initialItemCount = currentItemCount;
    }

    lastRowItemCount = initialItemCount % cols;
    lastRowFillerCount = (lastRowItemCount !== 0) ? cols - lastRowItemCount : 0;
    expectedItemCount = initialItemCount + lastRowFillerCount;
    
    $('.result').html('No: of lis in a row = ' + cols + '<br>' + 'No: of lis in last row = ' + lastRowItemCount);

    if (expectedItemCount !== currentItemCount) {
      if (currentItemCount > expectedItemCount) {
        var itemsToRemove = items.slice(initialItemCount + lastRowFillerCount);

        $.each(itemsToRemove, function(index, item) {
          fillersToRemove.push(item.element);
        });

        masonry.remove(fillersToRemove);
        masonry.layout();
      } else {
        fillersToAddCount = expectedItemCount - currentItemCount;
        fillerClassName = masonry.options.itemSelector.replace('.', '') + ' ' + fillerClassName;

        while (fillersToAddCount) {
          $el = $(fillerHtml).addClass(fillerClassName);
          $grid.append($el);
          fillersToAddCount = fillersToAddCount - 1;
        }

        masonry.reloadItems();
        masonry.layout();
      }
    }
  }

  var $grid = $('.grid');

  $grid.masonry({
    itemSelector: '.item',
    isFitWidth: true,
    gutter: 20
  });
  $grid.masonry('on', 'layoutComplete', toggleFillers);
  $grid.masonry('layout');
});
.grid {
  margin: 0 auto;
}
.item,
.filler {
  margin-bottom: 20px;
  border: 1px solid red;
  height: 80px;
  width: 80px;
}
.filler {
  background-color: #999;
  border: 1px solid blue;
}
<script type="text/javascript" src="//code.jquery.com/jquery-2.1.3.js"></script>
<link rel="stylesheet" type="text/css" href="#" onclick="location.href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css'; return false;">
<script type="text/javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/masonry/3.3.2/masonry.pkgd.min.js"></script>
<input type="hidden" name="hfTotalGridItems" id="hfTotalGridItems" value="10" />

<div class="grid">
  <div class="item">
    <div>lorem</div>
  </div>
  <div class="item">
    <div>lorem</div>
  </div>
  <div class="item">
    <div>lorem</div>
  </div>
  <div class="item">
    <div>lorem</div>
  </div>
  <div class="item">
    <div>lorem</div>
  </div>
  <div class="item">
    <div>lorem</div>
  </div>
  <div class="item">
    <div>lorem</div>
  </div>
  <div class="item">
    <div>lorem</div>
  </div>
  <div class="item">
    <div>lorem</div>
  </div>
  <div class="item">
    <div>lorem</div>
  </div>
</div>
<div class="result"></div>