Добрый день. Возможно, это не самая полезная статья, но я довольно часто я сталкивался с тем, что на сайте требовалось выстроить блоки в «мозаику», и всегда изобретал всякие велосипеды.

После прочтения урока о создании плагинов для jQuery, я решил попробовать написать эту статью, решающую мою проблему, смотрим что вышло.

Для начала давайте создадим jQuery плагин


(function( $ ) {
  $.fn.mozaika= function() {
  };
})(jQuery);


Как и в большинстве плагинов, в нашем будут настройки. Давайте определимся с ними:

  • Количество столбиков
  • Отступы между блоками
  • Поле (padding) сверху и снизу поля с «пазлами»
  • Класс для блоков
  • Класс для блоков с двойной шириной
  • Класс для блоков с тройной шириной
  • нижний Padding ячейки


Имеем


(function( $ ) {
  $.fn.mozaika= function() {
    var set = $.extend( {
	  'n': 4,
	  'margin': 4,
	  'padding' : 30,
	  'item' : 'item',
	  'citem2' : 'item-w2',
	  'citem3' : 'item-w3',
	  'itembot' : 0
   }, options);
  };
})(jQuery);

Создадим массив для высоты каждой колонки:

var cols = new Array() // Массив для высоты колонки
for(i = 0; i < set.n; i++)
	cols[i] = 0;

Найдём ширину элемента с мозаикой:

var width = this.width(); // ширина элемента с мозаикой

Следующим шагом нужно вычислить ширину одной ячейки (с обычной шириной):

var one = (width - set.margin * (set.n - 1))/set.n; 
	// ширина одного элемента
var left = 0; // позиция по Х

Следующим шагом стоит найти все ячейки и построить наш «пазл». Для этого нужно проверить, не являются ли наши ячейки — ячейками с двойной или тройной шириной. Если вдруг элемент с двойной шириной будет строиться на четвёртой колонке из четырёх, нужно перенести его в первую. Так-же для случаев с не одинарной шириной, нужно будет строить ячейку в самом низу большей из колонок. Так-что найдём максимальную высоту, из колонок которые затрагиваются.

Дальше зададим координаты нашей ячейке.

Следующим шагом запишем новую высоту нашего столбика(столбиков), и новые координаты по оси x. Если строка закончилась, переходим на новую. В конце — задаём высоту блока для наших ячеек.

(function( $ ) {
  $.fn.mozaika= function(options) {
	 var set = $.extend( {
		  'n': 4,
		  'margin': 4,
		  'padding' : 30,
		  'item' : 'item',
		  'citem2' : 'item-w2',
		  'citem3' : 'item-w3',
		  'itembot' : 0
		}, options);
	
	var cols = new Array() // Массив для высоты колонки
	for(i = 0; i < set.n; i++)
		cols[i] = 0;
	var width = this.width(); // ширина элемента с мозайкой
	var one = (width - set.margin * (set.n - 1))/set.n; 
	// ширина одного элемента
	var left = 0; // позиция по Х
	var moz = this.children('.'+set.item); // ячейки
	i = 0;
	$(moz).each(function( k, v ) {
		if($(v).hasClass(set.citem2)){
			w = one*2+set.margin;
			q=2;
		}else if($(v).hasClass(set.citem3)){
			w = one*3+set.margin*2;
			q=3;
		}else{
			w = one;
			q = 1;
		}
		if(i+q>set.n){
			i = 0;
		}
		var mx = cols[i];
		var mn = i;
		for(e = i; e <i+q; e++)
			if(cols[e] > mx){
				mx = cols[e];
				mn = e;
			}
  		$(v).css({'position':'absolute', 'width' : w+'px'});
		$(v).css({'left' : left+'px', 'top':mx, 'padding-bottom':set.itembot});
		$(v).animate({opacity:1},1000);
		hg = cols[i];
		for(e = i; e < i+q; e++){
			cols[e] = hg + $(v).height() + set.margin + set.itembot;
			//console.log('n ' + i + ' q ' + q + ' cols[' + e + '] ' + cols[e]);
		}
		left = left + set.margin + w;
		i = i+q;
		if(i>=set.n){
			i=0;
			left = 0;
		}
	});
	mx = cols[0];
		for(e = 0; e <set.n; e++)
			if(cols[e] > mx){
				mx = cols[e];
			}
	if(this.height()<(mx+set.padding*2))
		this.height(mx+set.padding*2);
  };
})(jQuery);

Вызываем:

$(document).ready(function(e) {
	$(".catalog").moZaika({
		'n': 4,
		'margin': 2,
		'item' : 'cat-item',
		'citem2' : 'cat-w2',
		'citem3' : 'cat-w3',
		'itembot' : 20,
		'padding' : 0
	});
});

Готово, всё работает!

image

Единственное с чем осталось побороться, это с перестроением при изменении ширины поля для мозаики. Для этого я пока-что ещё раз вызываю плагин в $(window).resize().

Комментарии (17)


  1. Isis
    08.01.2016 13:25
    +2

    1. neyrowebster
      08.01.2016 14:13

      Да, я его видел. Не знаю причин, но строило через раз.


      1. novoxudonoser
        08.01.2016 19:14
        +2

        Вы просто не смогли его приготовить. Работает он через раз из за асинхронной загрузки изображений. Надо использовать imagesLoaded пример — http://codepen.io/desandro/pen/RPKgEN


        1. neyrowebster
          08.01.2016 20:47

          благодарю


  1. hell0w0rd
    08.01.2016 14:25
    +2

    Шел 2016, люди все еще пишут jquery плагины. Судя по коду — совершенно спокойно можно сделать с помощью современного JS.


    1. neyrowebster
      08.01.2016 14:33

      для практики пойдёт.
      Честно, понимаю что нужно уходить от jQuery.
      Стараюсь.)
      Писал чтобы получить приглашение на хабр.


  1. Rastishka
    08.01.2016 15:10

    Мне кажется что такое уже давно должно делаться на чистом CSS, флексбоксами какими нибудь…
    Или пока еще невозможно?


    1. Akuma
      08.01.2016 15:26
      +1

      Можно вот так делать: http://w3bits.com/css-masonry/
      Но это немного не то, т.к. нет разнородных элементов. А в точности повторить масонри на чистом CSS пока, думаю, не получится.


  1. k12th
    08.01.2016 15:46

    var cols = new Array() // Массив для высоты колонки
    

    Есть только один повод писать new Array в JS — это если нужен массив undefined заданной длины. В принципе, вреда в этом нет, если человек понимает, что делает, но во многих code style порицается.

    for(i = 0; i < set.n; i++)
    	cols[i] = 0;
    

    А вот за это в приличных проектах канделябром-с:)

    Вообще этот кусок можно переписать (субъективно) красивее:
    var cols = (new Array(set.n)).map(_ => 0)
    

    Как минимум, короче.

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


    1. neyrowebster
      08.01.2016 16:27

      Спасибо!
      Хорошее замечание.


    1. neyrowebster
      08.01.2016 16:31

      не знаю точно что, но что-то тут не так. Попробовал, не работает(

      var cols = (new Array(set.n)).map(_ => 0);
      

      покручу ещё.


      1. k12th
        08.01.2016 16:36

        Это ES6, если что. Оно может где-то не работать без компиляции. В ES5 это будет так:

        var cols = new Array(set.n).map(function (_) {
          return 0;
        });
        


        1. neyrowebster
          08.01.2016 16:52

          спасибо.
          Понял.
          Пошёл познавать истину.


      1. irezvov
        10.01.2016 11:24

        это и не должно работать. map и forEach не работает на массивах, только что созданных через new Array(length). Вас ввели в заблуждение


        1. k12th
          10.01.2016 15:00

          Ваша правда, а мне пора на свалку:(


    1. hell0w0rd
      10.01.2016 19:43
      +1

      Тогда уж:

      let cols = new Array(set.n).fill(0);
      


  1. POPSuL
    09.01.2016 18:21

    Решал однажды подобную задачу. Сопротивлялся недели две, не желал это делать, ибо считал это убожеством, но за пол часа накидал прототип, который после пары мелких правок ушел в продакшен, и не менялся уже года 4 :)

    Это не реклама
    http://rinamika.ru/
    раньше работал в этой компании, и пилил эту мозайку
    Решил задачу таким вот говнокодом http://rinamika.ru/resources/javascript/mosaic.js.
    И не просите это прокомментировать!