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

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

Рис. 1. Порядок отображения группы товаров.

Стандартными элементами карточки товара являются фото товара, название, цена, кнопка «купить». Но часто появляются и дополнительные поля, например: «рейтинг», «отзывы», «нет в продаже», «акции / скидки», «старая / новая цена», описание товара и другие, которые могут влиять на стандартную высоту карточки товара, а поскольку размеры и наполнение контентом этих элементов варьируется для каждого товара индивидуально (например на каком-то товаре есть старая цена, на другом нет или название в несколько строк) то не только весь ряд у нас приобретает некорректный вид, а и влияет на отображение последующих элементов.

Рис. 2. Некорректное отображение при добавлении элементов, или изменении их высот.


Вариант с фиксированным размером карточки не очень подходит, так как:

  1. 90% моих работ — адаптивные сайты (минимум фиксированных размеров, в данном случае — высота карточки товара не должна быть фиксированной).
  2. Не получится рассчитать оптимальную высоту, чтобы подходила для всех вариантов.

Можно взять разбитие по системе Grid – один ряд по четыре товара (пример с упрощенной семантикой):

	<row>
		<col>
		<col>
		<col>
		<col>
	</row>

Но тогда возникают трудности с обработкой и выводом товаров на стороне сервера (необходимо усложнять выборку циклами и условиями), особенно, если товары выбираются из разных категорий — тогда у нас новая категория будет начинаться с нового ряда, а нам нужно вывести все товары подряд.

В идеале нам необходима следующая структура:

<div class="wrapper">
		<div class="product-card">...</div>
		<div class="product-card">...</div>
		<div class="product-card">...</div>
		<div class="product-card">...</div>
		………
		<div class="product-card">...</div>
</div>

И чтобы все выглядело ровно и красиво.

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

Итак! Перейдем ближе к технической части статьи:

  1. Функция написана с использованием библиотеки jQuery, по желанию легко переписывается на native js.
  2. В ней просто разобраться, и добавить (по необходимости) что-то свое.
  3. Самое важное! — она делает свое дело!

Структура DOM дерева используется как в примере выше:

	<div class="wrapper">
		<div class="product-card">...</div>
		<div class="product-card">...</div>
		<div class="product-card">...</div>
		<div class="product-card">...</div>
		………
		<div class="product-card">...</div>
	</div>

Принцип работы следующий:

  1. Узнаем ширину карточки товара «product-card»;
  2. Узнаем ширину родительской обертки «wrapper»;
  3. Производим расчет — сколько карточек влезет по ширине в обертку (округление в меньшую сторону) так мы получаем имитируемый ряд;
  4. Далее в работу вступают циклы:
    а) Сколько и каких элементов необходимо уравнять по высоте в одной карточке;
    б) Сравнивает эти высоты в каждой карточке имитируемого ряда, и находит наибольшее значение высоты сравниваемых элементов;
    в) Назначает соответствующие высоты всем элементам которые попали в область сравнения.

Ниже указан собственно сам скрипт (цифрами отмечены пункты из списка принципа работы):

  function GreatBalancer(block){
    var wrapWidth = $(block).parent().width(),  // 1
     blockWidth = $(block).width(),          // 2
     wrapDivide = Math.floor(wrapWidth / blockWidth),     // 3
     cellArr = $(block);
    for(var arg = 1;arg<=arguments.length;arg++) {           // 4.1
     for (var i = 0; i <= cellArr.length; i = i + wrapDivide) {
     var maxHeight = 0,
      heightArr = [];
     for (j = 0; j < wrapDivide; j++) {               // 4.2
     heightArr.push($(cellArr[i + j]).find(arguments[arg]));
      if (heightArr[j].outerHeight() > maxHeight) {
       maxHeight = heightArr[j].outerHeight();
      }
     }
     for (var counter = 0; counter < heightArr.length; counter++) {           // 4.3
      $(cellArr[i + counter]).find(arguments[arg]).outerHeight(maxHeight);
      }
     }
    }
  }

и его вызов:

GreatBalancer(".product-card",".product-title",".price-min",".product-image");

Обратите внимание! Первым аргументом вы должны указать карточку товара. Далее в любом порядке перечень тех элементов, которые необходимо уровнять, функция может принять и обработать любое число элементов!

Вот в качестве примера screenshot, на котором четко видно что, из-за различия высот элементов (title товара имеет разное количество строк, и в первом товаре появилась старая цена), первый ряд выглядит некорректно, а второй ряд сместился.

Рис. 3. Пример отображения группы товаров без выравнивания.


А на следующем изображении отчетливо видно, как изменилось соотношение высот элементов:

Рис. 4. Пример результата работы скрипта.


Буду рад, если эта статья пришлась кому то на пользу! Всем успехов, интересных проектов и нестандартных решений!

P.S.. В нашей школе скоро стартуют курсы по Front end от автора статьи «Как быстро начать программировать в Node.js», «HTML5, CSS3 и JavaScript, что это и с чем его едят?» и «Курсы программирования JavaScript». Чтобы записаться пишите на info@digitov.com

P.P.S. Чтобы получать наши новые статьи раньше других или просто не пропустить новые публикации — подписывайтесь на нас в Facebook, VK и Twitter.

Автор:
Станислав Закорко, Senior JavaScript Developer, компания «SECL Group»
Поделиться с друзьями
-->

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


  1. bromzh
    29.11.2016 13:17
    +20

    Зачем js? Чем flexbox не угодил?


    1. SECL
      29.11.2016 13:44
      -17

      Данная статья предлагает один из методов решения проблемы, так как в некоторых случаях flex не подходит. Например если мы верстаем под поддержку более ранних версий IE и других браузеров, или используем в работе сетку grid встречаемую в Bootstrap или другом подобном наборе инструментов


      1. bromzh
        29.11.2016 13:55
        +8

        http://caniuse.com/#feat=flexbox — 97%. Оставшимся 3-м можно намекнуть про современный мир. А чтобы они не сильно расстраивались из-за кривой вёрстки в их, можно подключить modernizr и подключать js-решения.


        Фреймворки с гридами надо выбирать нормальные. 4-й бутстрап может сетку через flex делать, почему бы не использовать его? Плюс, флекс — это не только сетка, это ещё куча крутых возможностей. А проблема отсутствия поддержки флекса всё больше становится гипотетической, нежели реальной.


        1. dom1n1k
          29.11.2016 19:53

          Я в целом поддерживаю решение с флексбоксом, но нужно понимать, что статистика caniuse — это средняя температура по больнице. Я знаю реальные сайты с такой аудиторией, где поддержка флекса менее 90%.
          И намекнуть проблематично — обычно люди сидят на старых браузерах не из вредности, а есть какие-то вынуждающие факторы.


    1. Zenitchik
      29.11.2016 13:46
      -5

      Тем, что приходится поддерживать IE8.


      1. bromzh
        29.11.2016 13:57
        +1

        Зачем? Это реальная надобность или так сказали? Ну и опять же, что мешает подключить modernizr и добавить фолбэк на случай старых ослов?


        1. Zenitchik
          29.11.2016 19:03
          +2

          Это реальная надобность. За это платят. А за фолбэк как раз и надо писать методом, описанным в статье.


      1. symbix
        29.11.2016 15:15
        +5

        Для IE8 можно тупо воткнуть заведомо достаточный фиксированный height и фиг с ним.

        Для такого хлама нет никакого смысла делать красиво — достаточно, чтобы можно было пользоваться.


        1. PaulZi
          29.11.2016 16:30
          +2

          Для хлама выставить display:inline-block; и пофиг на прыгающую высоту — пусть страдают, главное чтобы сетка товаров не нарушалась.


        1. Zenitchik
          29.11.2016 19:04

          Смотря, сколько платят.


          1. symbix
            30.11.2016 15:50

            Я обычно объясняю принцип graceful degradation. Но если человек настаивает — ради бога, любой каприз за ваши деньги, конечно.


    1. set
      29.11.2016 15:00

      Зачем js? Чем flexbox не угодил?

      Как мне кажется, этот скрипт решает чуть больше проблем, чем может решить флексбокс. Т.е. сами карточки сделать одинаковой высоты можно и флексбоксом, но как с помощью флексбокса сделать одинаковыми по высоте внутренние элементы соседних карточек? Насколько я понял, этот скрипт подобную проблему решает, а флексбокс — нет.


      1. bromzh
        29.11.2016 16:07

        Да, внутренние элементы уже нельзя выровнять (хотя изначально. Но если смотреть на примеры из статьи, то лучше было бы не выводить длинный текст целиком, а использовать text-overflow: elipsis;.


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


        1. set
          29.11.2016 18:14
          +1

          Да, внутренние элементы уже нельзя выровнять (хотя изначально. Но если смотреть на примеры из статьи, то лучше было бы не выводить длинный текст целиком, а использовать text-overflow: elipsis;.

          к сожалению, у верстальщика не всегда есть возможность связаться с дизайнером и повлиять на это. Т.е. если нарисовано, что размеры элементов могут быть разные, но при этом должны выравниваться, то надо это реализовывать. И без скриптов это сделать, к сожалению, не всегда получается.


  1. raveclassic
    29.11.2016 13:30
    +8

    Не получится рассчитать оптимальную высоту, чтобы подходила для всех вариантов.

    Не выдумывайте и используйте flexbox


    1. SECL
      29.11.2016 13:44
      -5

      Ответил выше


      1. Focushift
        29.11.2016 18:53
        +3

        Это хабр, а хабр не признает существование «кровавого» Энтерпрайза с ИЕ8.


  1. Aingis
    29.11.2016 13:38
    +2

    И зачем на JS решать задачи вёрстки, когда есть а) флексбоксы (почти все браузеры), б) старая статья «Список блоков с разным вертикальным выравниванием» (с ограничениями, но тут можно приспособить)?


    1. set
      29.11.2016 15:04
      +1

      И зачем на JS решать задачи вёрстки, когда есть а) флексбоксы (почти все браузеры), б) старая статья «Список блоков с разным вертикальным выравниванием» (с ограничениями, но тут можно приспособить)?

      Если у карточки товара более 1 элемента с плавающей высотой, а уровнять нужно все, то этот способ не подойдёт, к сожалению.


      1. Aingis
        29.11.2016 17:43
        +1

        Всё равно остаются флексбоксы, они работают почти везде. Если надо, с деградацией до инлайн-блоков. Пользователи устаревших браузеров переживут неодинаковость высоты. Ваш скрипт ещё и неоптимален по быстродействию: будет форсироваться перерасчёт стилей на каждом ряду.


        1. set
          29.11.2016 18:11

          Как в ситуации, когда нужно выровнять не только сами карточки, но и их содержимое по соседним элементам, поможет флексбокс? Имею ввиду нечто подобное — ссылка — пример утрированный, но вполне отображает суть проблемы — сами карточки товара одинаковой высоты, а вот внутренние элементы у соседних карточек совсем не по сетке и разной высоты. Скрипт автора статьи решает эту задачу в том числе. Если подскажете, как подобное реализовать на флексбоксе — буду очень благодарен.
          PS я к скрипту этому не имею никакого отношения.


          1. Aingis
            29.11.2016 21:58

            Это вам уже сетка нужна, то есть двумерная раскладка. На это способны только таблицы (display: table-*) или CSS Grid Layout (который в проекте, хоть и в финальной стадии, для вашего случая особенно интересны subgrid). Флексбоксы работают только в одном измерении (хотя перенос по рядам довольно сподручен). Скрипт же уравнивает общую высоту и только, тоже что и делает флексбокс по умолчанию.


            1. set
              30.11.2016 10:08

              Скрипт же уравнивает общую высоту и только, тоже что и делает флексбокс по умолчанию.

              Видимо, вы совершенно невнимательно читали статью:
              GreatBalancer(".product-card",".product-title",".price-min",".product-image");


              Обратите внимание! Первым аргументом вы должны указать карточку товара. Далее в любом порядке перечень тех элементов, которые необходимо уровнять, функция может принять и обработать любое число элементов!

              т.е. скрипт делает именно то, о чём мы с вами говорили. Вот вам мой же пример, но с подключенным скриптом — ссылка
              P.S. — скрипт как раз таки уравнивает _только_ внутренние элементы, не трогая сами карточки почему-то. т.е. если я перечислю не все внутренние элементы или вообще не перечислю, то никакого выравнивания вообще не будет…


  1. sashabeep
    29.11.2016 13:44

    Изобрел eqheught


    1. SECL
      29.11.2016 14:04
      -2

      bootstrap equal height тоже использует флекс. Он не удовлетворяет требованиям для разработки коммерческих проектов. По статистике IE до сих находится в верху списка по популярности. Особенно им пользуются организации с лицензионным ПО, и об этом стоит помнить.


      1. sashabeep
        29.11.2016 14:06
        +5

        А я говорил про древний eqheight.js
        Про флексы выше отписались — это всё пуки в пузырьки про «почти все браузеры». Лет через 5
        В Foundation тоже на JS, но, если мне не изменяет память, если собрать с флексовыми гридами, будет тоже на них


  1. sinneren
    29.11.2016 13:45

    Давно есть скрипт matchHeight именно для этой задачи


    1. SECL
      29.11.2016 15:19
      -2

      Это целая библиотека. А у нас маленькое читаемое решение конкретной задачи


  1. Clever_Coyote
    29.11.2016 14:04

    Если саппортить старые ie то вполне сойдет, в ином случае согласен со всеми ко советовал flexbox


  1. webdevium
    29.11.2016 14:13

    Я бы функцию назвал GreatEqualizer.


  1. jrip
    29.11.2016 14:39

    >и нестандартных решений!
    Это как бы стандартное костыльное решение.
    В 99% случаев нужно просто выставить одну и туже высоту блоку с изменяемым текстом и всё, т.к. один фиг если строки с этими блоками будут разными по высоте выглядеть это будет не очень.


  1. ertaquo
    29.11.2016 15:19
    +2

    Блин, самая первая картинка глаза аж режет


    1. set
      29.11.2016 18:19
      +3

      да, автор шутник, видимо :) я думал, мне показалось.

      но нет, не показалось :)
      image


      1. Antelle
        29.11.2016 22:11

        Спейсинг в коде — вообще огонь.


  1. KasperGreen
    29.11.2016 20:49

    Автор, джаваскрипт тебя подери! Я же читал… и перестал на слове function… Велосипеда тебе уникального, тёплого и лампового.


  1. set
    30.11.2016 10:41

    Уважаемый автор! До того момента, как вы добавили свои постскриптумы с рекламой уроков по js, статья выглядела, как обычная попытка начинающего яваскриптера изобрести «серебряную пулю». Теперь же даже не знаю, как её воспринимать. Мало того, что вы забыли объявить переменную в коде (в данном случае это не страшно, особенно, если автор — новичок), так ещё и ваши примеры походят на примеры «как нарисовать сову» — только что ещё в некоторых карточках не было старой цены вовсе и уже на следующем скриншоте — тадам! у нас там точки вместо отсутствующих элементов. Т.е. складывается впечатление, что ваш скрипт это сделал. Однако же, это не так. Если какой-то элемент будет отсутствовать в коде (хотя бы один, самый последний), то фактически выравнивание карточек не произойдёт, не смотря на то, что их внутренние элементы выровняются. Опять же — почему скриншоты и только? Где живой пример «до» и живой пример «после»? Ведь судя по комментариям вы даже не смогли донести свою идею. Большинство комментирующих явно не догадались о том, что вы выравниваете не сами карточки, а их содержимое.
    Кроме того, в начале статьи вы упоминаете адаптивность и т.п., но по факту ваш скрипт ну совершенно никак с ней не работает.
    Повторюсь, это не страшно, решаемо и вполне допустимые оплошности для новичка, но никак не для человека, который настолько владеет предметом, что преподаёт его другим. Большей антирекламы, чем ваша же статья, вашим курсам придумать сложно.


    1. raveclassic
      30.11.2016 11:21

      Там совсем в конце самая мякотка:

      Автор:
      Станислав Закорко, Senior JavaScript Developer, компания «SECL Group»


  1. barbaricus
    30.11.2016 11:55

    Если действительно так необходимо поддерживать IE8 и ниже (или для каких браузеров автор так заморочился?), то решение нормальное. Хотя я почти уверен, что подобное решение не зря называется «велосипедом» — он у каждого, как и полагается, свой.

    Другое дело — стоит ли поддерживать настолько устаревшие браузеры? В чём реальная необходимость? Я понимаю, что не всегда требования к совместимости зависят от разработчика (даже если он ведущий), но поддерживать в конце 2016 года тот же IE8 — это какое-то поклонение культу смерти. Ну то есть можно, конечно, создать сайт, который поддерживает все браузеры последних лет 20-ти и порадовать этим заказчика, обладающего смутными представлениями о здравом смысле, но зачем? Мало того, что усложняется поддержка такого кода, так ещё и разработчики таким образом продлевают жизнь реликтовым видам браузеров.

    Удивляют люди/компании, которые вместо того, чтобы научиться говорить заказчику/шефу/внутреннему идеалисту «нет» и с чистой совестью развивать (или хотя бы использовать) современные технологии начинают заниматься археологией себе на голову.

    Это моё сугубо личное мнение, разумеется. Не хочу никого обидеть, тем более — автора и его коллег.


    1. Zenitchik
      30.11.2016 13:08
      +1

      Я с Вами согласен. В абсолютном большинстве случаев IE ниже 9 поддерживать ни к чему.
      Но вот конкретно в моей практике есть клиент, у которого 200000 (двести тысяч) сотрудников, у которых на рабочих машинах IE8, и которые должны иметь доступ пусть не к полному, но более-менее достаточному функционалу приложения и не в расползающемся дизайне. А что самое главное — клиент за этот геморой платит. И платит достаточно много, чтобы не отказываться от него.
      Поэтому меня задевают вопросы «зачем велосипед, если есть современный способ». Да, есть-то он есть, и в полной версии системы он у меня тоже есть. Но фолбэк-то я тоже как-то должен писать. Так почему бы не быть на Хабре статье о старых технологиях?


      1. bromzh
        30.11.2016 17:08

        Пускай статьи будут. Но на мой взгляд, в таких статьях нужно обязательно упоминать про современные альтернативы. И ещё крайне желательно упомянуть о минусах описанного подхода. Например, в данном случае скорее всего будут проблемы с производительностью на большом количестве элементов, да и некоторые стили из css станут работать не совсем так, как задумывалось. Уж сеньёры должны знать о подводных камнях, так почему бы не описать их подробнее?


        1. Zenitchik
          30.11.2016 18:17

          Согласен. И подчёркивать, что область применения — костыли для старого осла.


  1. marsdenden
    30.11.2016 11:55
    +2

    Автор, не слушай про flexbox! JS тоже нужен, флексом не все сделаешь. Спасибо за скрипт, положу в копилку


  1. ElenRoze
    30.11.2016 11:56

    Для карточек
    .card {
    display: inline-block;
    vertical-align: top;
    }
    не? Не пробовали?


  1. lifestyle
    30.11.2016 18:38
    -1

    Отличный пост, года эдак для 2007-го)