Pull to refresh

Вертикальные отступы между колонками с помощью Sass на примере bootstrap сетки

Reading time5 min
Views24K
Практически любой сайт не обходиться без блоков потоковых элементов, таких как: список новостей, товаров, фотографий галереи. Такие элементы в основном выводятся шаблонизатором в цикле, занимают равное число колонок и непредвиденное количество строк. Если такие элементы переходят на вторую строчку, то между ними нужно предусмотреть вертикальные отступы, что бы они не сжимались. Даже если на макете дизайнер для красоты вывел только один ряд, на верстке обязательно нужно предусмотреть увеличение числа таких элементов
Вертикальные отступы между колонками
Задача, вроде бы проще простого, о чем тут вообще говорить. Но давайте все таки разберем подробнее способы решения такой задачи, так как подводные камни присутствуют.

Отступ для каждого элемента


Представляем что разметка у нас такая:
<ul class="gallery">
  <li class="gallery__image"></li>
</ul>

Но проблема тут в том, что сам родительский блок будет отступаться от нижних на 20px, он не будет изолирован от внешней среды, будет влиять на геометрию соседних блоков. Его будет сложно переносить в другие места сайта или на другие проекты. Одним словом, блок галереи не будет независим.

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

Отрицательный отступ для блока


Делаем для каждого элемента вертикальный отступ а для родителя отрицательный вертикальный отступ такой же величины.
.gallery{
  margin-top: -20px
}
.gallery__image{
  margin-top: 20px
}

Все, вроде бы отлично, но блок если в ну область, которую перекрывает блок попадет интерактивный элемент, например ссылка, то блок ее перекроет. Вот на эти ссылки, что на скрине наведение работать не будет.
Отрицательный отступ для блока
Из этой ситуации можно выйти. Нужно обернуть блок галереи в еще один блок, которому задать overflow: hidden и он обрежет все переполнение.
.gallery{
  overflow: hidden;
}
.gallery__inner{
  margin-top: -20px
}
.gallery__image{
  margin-top: 20px
}

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

Отступ для каждого элемента, кроме n первых


В идеале нужно указать верхний отступ для всех элементов кроме первого ряда. Это можно сделать с помощью псевдокласса nth-child

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

Но при использовании препроцессоров такое решение можно сделать гибким, что собственно я и хочу вам продемонстрировать. За основу я взял Bootstrap сетку, то-есть каждая фотография галерее у нас помещена в колонку сетки. В качестве препроцессора я использовал Sass. Но данный подход можно смело применить к другим сеткам и препроцессорам.

Напомню, что колонки в bootstrap сетке записываются следующими классами:
col-xs-*
col-sm-*
col-md-*
col-lg-*

Давайте представим, что наша галерея заключена в 3 колонки на средних размерах экрана, то-есть col-md-4. Для универсальности отступы будем делать для колонок сетки, у которой присвоен модификатор row-vertical-indent.
<div class=”row row--vertical-indent”>
  <div clas=”col-md-4”>
    <div class=”gallery-item”>
      <img...>
    </div>
  </div>
</div>

Теперь пишем Sass стили
.row-vertical-indent{
  & > [class~="col-md-4"]:nth-child(n+4){
    padding-top: 20px;
  }
}

В результате все элементы начиная с 4-го будут иметь отступ сверху. Первые 3 будут без отступов.

Давайте теперь расширим возможности и предусмотрим любое число колонок на экранах среднего размера
.row-vertical-indent{
  @for $i from 1 through $grid-columns{
    $nth-element: floor(($grid-columns / $i) + 1);
    & > [class~="col-md-#{$i}"]:nth-child(n+#{$nth-element}) {
      padding-top: 20px;
    }
  }
}

Переменная $grid-columns — это внутренняя переменная bootstrap, которой присвоено количество колонок сетки. По умолчанию 12.

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

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

Создаем обьект (не могу привыкнуть к терминологии “мап”) медиа-запросов.
Ключ — это обозначение брейк-поинта в названии класса, то-есть col-xs.., col-sm.., col-md…
Значение — это уже сами медиа-виражения для каждого брейк-поинта.
$break-points: (
  'xs': '(max-width: #{$screen-xs-max})',
  'sm': '(min-width: #{$screen-sm-min}) and (max-width: #{$screen-sm-max})',
  'md': '(min-width: #{$screen-md-min}) and (max-width: #{$screen-md-max})',
  'lg': '(min-width: #{$screen-lg-min})'
);

Дальше пробегаемся по этому обьекту и для каждой итерации применяем предыдущею конструкцию, но уже для конкретного медиа-виражения.
@each $key, $val in $break-points{
  @media #{$val}{
    @for $i from 1 through $grid-columns{
      $nth-element: floor(($grid-columns / $i) + 1);
      & > [class~="col-#{$key}-#{$i}"]:nth-child(n+#{$nth-element}){
        padding-top: $gutter;
      }
    }
  }
}

Не забываем в названии селекторы вместо md прописать значение ключа обьекта. #{$key}

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

В результате у нас получается вот такая примесь, которой можно расширить возможности сетки bootstrap.
@mixin grid-vetical-gutter($gutter){
  $break-points: (
    'xs': '(max-width: #{$screen-xs-max}) ',
    'sm': '(min-width: #{$screen-sm-min}) and (max-width: #{$screen-sm-max})',
    'md': '(min-width: #{$screen-md-min}) and (max-width: #{$screen-md-max})',
    'lg': '(min-width: #{$screen-lg-min})'
  );

  @each $key, $val in $break-points{
    @media #{$val}{     
      @for $i from 1 through $grid-columns{
        $nth-element: floor(($grid-columns / $i) + 1);
        & > [class~="col-#{$key}-#{$i}"]:nth-child(n+#{$nth-element}){
          padding-top: $gutter;
        }
      }
    }
  }
}

Теперь для нужного класса просто вызываем примесь передавая отступ
.row-vertical-indent{
  @include grid-vetical-gutter(20px)
}

Ограничения и недостатки такого подхода:

1 — Работает корректно только для одинакового числа колонок в строке.
То-есть для динамического потока колонок, которые выводятся в цикле (каталог товаров, новости, галерея итд).

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

2 — Нужно обязательно указывать для колонки все брейк-поинты.
Мое решение не предусматривает mobile first подход и стили не будут применяться на экране, для которого не указано количества колонок.

В bootstrap такая запись означает, что на будет 3 колонки для всех размеров начиная с sm.
Но при использовании вертикальных отступов число колонок обязательно нужно указать для всех размеров, то-есть запись будет выглядеть так
<div class==”col-xs-12 col-sm-4 col-md-4 col-lg-4”>

Я пробовал предусмотреть mobile first подход, но столкнулся с ужасным гемором, связанным с переопределением стилей, что в результате забил. Если у кого получиться, дайте знать, буду очень признателен.

3 — Жесть в скомпилированном CSS.
Если кому-то придется править скомпилированный css-файл, то там будет очень много мусора. Мне лично не принципиально, но мало ли.
Tags:
Hubs:
+9
Comments3

Articles