«Великий уравнитель» или способ решить проблему выравнивания по высоте

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

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

    Рис. 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»
    SECL Group
    46,00
    Делаем стартапы успешными!
    Поделиться публикацией

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

      +20

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

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

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


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

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

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

              +2
              Это реальная надобность. За это платят. А за фолбэк как раз и надо писать методом, описанным в статье.
              +5
              Для IE8 можно тупо воткнуть заведомо достаточный фиксированный height и фиг с ним.

              Для такого хлама нет никакого смысла делать красиво — достаточно, чтобы можно было пользоваться.
                +2
                Для хлама выставить display:inline-block; и пофиг на прыгающую высоту — пусть страдают, главное чтобы сетка товаров не нарушалась.
                  0
                  Смотря, сколько платят.
                    0
                    Я обычно объясняю принцип graceful degradation. Но если человек настаивает — ради бога, любой каприз за ваши деньги, конечно.
                0
                Зачем js? Чем flexbox не угодил?

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

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


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

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

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

                Не выдумывайте и используйте flexbox
                  –5
                  Ответил выше
                    +3
                    Это хабр, а хабр не признает существование «кровавого» Энтерпрайза с ИЕ8.
                  +2
                  И зачем на JS решать задачи вёрстки, когда есть а) флексбоксы (почти все браузеры), б) старая статья «Список блоков с разным вертикальным выравниванием» (с ограничениями, но тут можно приспособить)?
                    +1
                    И зачем на JS решать задачи вёрстки, когда есть а) флексбоксы (почти все браузеры), б) старая статья «Список блоков с разным вертикальным выравниванием» (с ограничениями, но тут можно приспособить)?

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

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


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

                            т.е. скрипт делает именно то, о чём мы с вами говорили. Вот вам мой же пример, но с подключенным скриптом — ссылка
                            P.S. — скрипт как раз таки уравнивает _только_ внутренние элементы, не трогая сами карточки почему-то. т.е. если я перечислю не все внутренние элементы или вообще не перечислю, то никакого выравнивания вообще не будет…
                    0
                    Изобрел eqheught
                      –2
                      bootstrap equal height тоже использует флекс. Он не удовлетворяет требованиям для разработки коммерческих проектов. По статистике IE до сих находится в верху списка по популярности. Особенно им пользуются организации с лицензионным ПО, и об этом стоит помнить.
                        +5
                        А я говорил про древний eqheight.js
                        Про флексы выше отписались — это всё пуки в пузырьки про «почти все браузеры». Лет через 5
                        В Foundation тоже на JS, но, если мне не изменяет память, если собрать с флексовыми гридами, будет тоже на них
                      0
                      Давно есть скрипт matchHeight именно для этой задачи
                        –2
                        Это целая библиотека. А у нас маленькое читаемое решение конкретной задачи
                        0
                        Если саппортить старые ie то вполне сойдет, в ином случае согласен со всеми ко советовал flexbox
                          0
                          Я бы функцию назвал GreatEqualizer.
                            0
                            >и нестандартных решений!
                            Это как бы стандартное костыльное решение.
                            В 99% случаев нужно просто выставить одну и туже высоту блоку с изменяемым текстом и всё, т.к. один фиг если строки с этими блоками будут разными по высоте выглядеть это будет не очень.
                              +2
                              Блин, самая первая картинка глаза аж режет
                                +3
                                да, автор шутник, видимо :) я думал, мне показалось.
                                но нет, не показалось :)
                                image
                                  0

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

                                0

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

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

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

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

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

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

                                          0
                                          Согласен. И подчёркивать, что область применения — костыли для старого осла.
                                      +2
                                      Автор, не слушай про flexbox! JS тоже нужен, флексом не все сделаешь. Спасибо за скрипт, положу в копилку
                                        0
                                        Для карточек
                                        .card {
                                        display: inline-block;
                                        vertical-align: top;
                                        }
                                        не? Не пробовали?
                                          –1
                                          Отличный пост, года эдак для 2007-го)

                                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                          Самое читаемое