В газете длинную заметку не печатают одной строкой во всю ширину листа. Её делят на несколько узких колонок: глаз быстро пробегает короткую строку, перескакивает на следующую и не теряется. В CSS для такой раскладки есть отдельный модуль — Multi-column Layout, или просто «колонки». Он появился задолго до Grid и Flexbox, работает во всех браузерах и при этом частично забыт: многие сразу тянутся к Grid там, где хватило бы одного свойства.
Разберём, чем колонки отличаются от Grid, какие свойства у них есть, как не разрезать карточку пополам и где колонки откровенно мешают. В конце — две свежие возможности: перенос колонок на новую строку и карусель без JavaScript.
Колонки и Grid: в чём разница
Grid и Flexbox раскладывают элементы: каждый дочерний блок занимает свою ячейку или своё место в ряду. Колонки работают иначе: они берут поток содержимого — текст, абзацы, картинки — и наполняют им контейнеры сверху вниз, как будто разливают воду по стаканам. Заполнилась первая колонка — текст перетекает во вторую, потом в третью.
<article class="news">
<p>Первый абзац...</p>
<p>Второй абзац...</p>
<p>Третий абзац...</p>
</article>
/* Колонки: абзацы перетекают из колонки в колонку */
.news {
column-count: 3;
}
/* Grid: каждый абзац — отдельная ячейка */
.news-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
Разница видна сразу. В Grid получится три ячейки по одному абзацу: короткий абзац оставит под собой пустоту, длинный растянет весь ряд. В колонках браузер сам поделит текст на три примерно равные по высоте части, и один абзац может начаться в первой колонке, а закончиться во второй.
Отсюда простое правило выбора: если нужно поделить на части текст или однотипный список — это колонки. Если нужно расставить самостоятельные блоки по сетке с выравниванием по строкам — это Grid. Блок, к которому применили колонки, в спецификации называют multicol-контейнером (от multi-column).
Сколько колонок: column-count и column-width
Включить колонки можно двумя способами: задать их количество или их ширину.
Фиксированное число колонок
column-count принимает целое число. Сколько бы ни было места, колонок будет ровно столько.
.glossary {
column-count: 3;
}
Минус такого подхода всплывает на телефоне: три колонки на экране шириной 360 пикселей — это строки по два-три слова. Придётся добавлять медиазапрос и уменьшать количество.
Колонки по ширине
column-width задаёт желаемую ширину колонки, а количество браузер считает сам: сколько колонок такой ширины влезает в контейнер, столько и будет.
.glossary {
column-width: 16em;
}
Важная деталь: это не точная ширина, а ориентир. Если в контейнер шириной 700 пикселей влезает две колонки по 16em и остаётся лишнее место, браузер не оставит его пустым, а растянет обе колонки. А если контейнер уже 16em, колонка будет одна и сузится до ширины контейнера. Получается адаптивная раскладка без единого медиазапроса: на широком экране три-четыре колонки, на телефоне одна.
Ширину удобнее задавать в em или ch, а не в пикселях: тогда она зависит от размера шрифта и строка остаётся комфортной для чтения при любом кегле.
Оба свойства сразу и сокращение columns
Если указать и ширину, и количество, column-count становится потолком: колонок не больше трёх, но и не уже 14em каждая.
.tags {
column-width: 14em;
column-count: 3;
}
/* То же самое одним свойством */
.tags {
columns: 14em 3;
}
На широком мониторе это защищает от десяти колонок подряд, на узком — от слишком тонких. Сокращение columns понимает значения в любом порядке: число браузер считает количеством, длину — шириной. Записи columns: 3 и columns: 16em тоже рабочие.
Отступы и линии между колонками
Промежуток между колонками задаёт column-gap — то же свойство, что и в Grid. По умолчанию у колонок он не нулевой, а равен 1em, чтобы текст соседних колонок не слипался.
Вертикальную линию по центру промежутка рисует column-rule. Синтаксис как у border: толщина, стиль и цвет.
.news {
columns: 18em;
column-gap: 3em;
column-rule: 1px dashed #94a3b8;
}
Две особенности линии:
- Она не занимает места. Если сделать линию толще, чем промежуток, она наедет на текст, а колонки не раздвинутся. Ширину промежутка всегда задаёт только column-gap.
- Линия рисуется только между колонками, в которых есть содержимое. Если последняя колонка пустая, линии перед ней не будет.
Заголовок на все колонки и высота колонок
column-span: all
В газете заголовок заметки тянется над всеми колонками, а не сидит в первой. Для этого у элемента внутри multicol-контейнера есть column-span: all.
.news h2 {
column-span: all;
margin-block: 1em;
}
У свойства два значения: none (по умолчанию) и all. Растянуть элемент на две колонки из трёх нельзя. Работает оно только на блочных элементах, которые стоят в обычном потоке: у плавающего (float) или абсолютно спозиционированного элемента ничего не произойдёт.
Интересно, что происходит с текстом вокруг такого заголовка. Всё, что стоит до него, браузер раскладывает в свой набор колонок и выравнивает по высоте. Всё, что после, начинается заново под заголовком. Поэтому подзаголовки с column-span: all делят длинный текст на независимые разделы, каждый со своими колонками.
column-fill: выравнивать или заполнять по очереди
По умолчанию браузер старается сделать колонки одинаковой высоты. Это поведение задаёт column-fill: balance. Альтернатива — column-fill: auto: колонки заполняются по очереди, первая до самого низа, потом вторая, и так далее. Последняя может остаться полупустой или вовсе пустой.
.schedule {
columns: 3;
height: 20em; /* без высоты auto не работает */
column-fill: auto;
}
Ловушка здесь в высоте. Колонкам нужно знать, где у них низ. Если у контейнера нет заданной высоты, Chrome просто игнорирует auto и выравнивает колонки как обычно. Firefox ведёт себя по-другому: он честно заполняет первую колонку, и весь текст оказывается в ней. Так что column-fill: auto имеет смысл только вместе с height или max-height.
Как не разрезать блок между колонками
Колонки делят поток где угодно, в том числе посреди элемента. Для абзаца это нормально, а вот карточка товара, у которой картинка осталась в первой колонке, а цена уехала во вторую, выглядит как баг.
Управлять разрывами помогают три свойства из спецификации о фрагментации — так называют деление содержимого на части: на колонки, на страницы при печати.
- break-inside — можно ли разрывать сам элемент;
- break-before — разрыв перед элементом;
- break-after — разрыв после элемента.
На практике чаще всего нужен один приём: запретить разрыв внутри карточки, цитаты или картинки с подписью.
.cards {
columns: 15em;
}
.card,
figure,
blockquote {
break-inside: avoid;
}
Ключевое здесь слово avoid — «избегать», а не «запретить». Это пожелание, а не жёсткое правило. Если карточка выше колонки (например, у column-fill: auto с маленькой высотой), браузеру всё равно придётся её разрезать: другого выхода нет.
С break-before и break-after осторожнее. Значение column («начать с новой колонки») внутри колонок пока понимают только браузеры на Chromium, Firefox и Safari его игнорируют. Поэтому принудительный перенос в новую колонку не стоит делать частью раскладки, которая без него развалится.
Где колонки помогают, а где мешают
Хорошие кейсы
Колонки отлично справляются с короткими однотипными списками, где порядок «сверху вниз» естественен:
- ссылки в подвале сайта;
- облако тегов или список категорий;
- глоссарий и алфавитный указатель;
- список фич или пунктов в лендинге.
.footer-links {
columns: 10em 4;
column-gap: 2em;
list-style: none;
}
.footer-links li {
break-inside: avoid;
margin-block-end: 0.5em;
}
Здесь не нужно ни считать количество колонок, ни писать медиазапросы, ни заворачивать ссылки в группы: список из любого числа пунктов сам разложится в колонки нужной ширины.
Кладка из карточек
Колонки — самый простой способ сделать кладку: карточки разной высоты стоят плотно, без пустот под короткими. Именно так выглядят доски с картинками.
.gallery {
columns: 14em;
column-gap: 1em;
}
.gallery img {
display: block;
width: 100%;
margin-block-end: 1em;
break-inside: avoid;
}
Но у этого способа есть подвох: карточки идут по колонкам, а не по строкам. Первая колонка — карточки 1, 2, 3, 4, вторая — 5, 6, 7, 8. Если карточки отсортированы по дате, самые свежие окажутся не в верхнем ряду, а в левой колонке, и пользователь читает их в неожиданном порядке. Для галереи без смысла в порядке это неважно, для ленты новостей — критично.
Когда колонки мешают
- Длинный текст выше экрана. Если колонка не помещается по высоте в окно, читатель доходит до низа первой колонки, потом скроллит вверх ко второй, потом опять вниз. На бумаге это нормально, на экране утомляет. Длинную статью лучше оставить в одну колонку, а ширину строки ограничить через max-width: 70ch.
- Навигация с клавиатуры. Порядок фокуса по Tab совпадает с порядком в разметке, то есть идёт вниз по первой колонке, потом по второй. В списке это ожидаемо, а в кладке, где глаз читает слева направо, фокус прыгает непредсказуемо.
- Сетка, где важно выравнивание по строкам. Карточки товаров с ценой на одном уровне, таблица тарифов, форма — это задача для Grid. Колонки не умеют выравнивать соседние элементы по горизонтали.
Поддержка браузерами
Базовый модуль колонок поддерживается всеми браузерами без префиксов уже много лет:
Отдельные свойства догоняли позже: column-span появился в Firefox только в 71-й версии, а break-inside: avoid внутри колонок — в 65-й. Сегодня всё это работает везде.
Новое: перенос колонок на новую строку
У классических колонок есть давняя проблема. Если задать контейнеру высоту, а текста окажется больше, лишние колонки не уходят вниз, а выстраиваются вправо, за край контейнера. На странице появляется горизонтальная прокрутка.
Вторая версия спецификации добавила два свойства, которые это исправляют:
- column-height — высота одного ряда колонок;
- column-wrap — что делать с лишними колонками: nowrap — ставить вправо, как раньше, wrap — переносить на новый ряд ниже.
.magazine {
column-count: 3;
column-height: 18em;
column-wrap: wrap;
row-gap: 2em;
}
Получается что-то вроде журнального разворота: три колонки высотой 18em, под ними следующие три, и так далее. Отступ между рядами задаёт привычный row-gap. Значение по умолчанию у column-wrap — auto: если задана column-height, оно работает как wrap, а без неё — как nowrap. То есть строку с column-wrap в примере выше можно и не писать.
Высоту ряда можно указать и в сокращении columns через слеш: columns: 3 / 18em.
Пока это работает только в браузерах на Chromium:
Поэтому включаем перенос через директиву @supports, а для остальных браузеров оставляем обычные колонки без фиксированной высоты. Тогда никто не увидит горизонтальной прокрутки: в старом браузере текст просто пойдёт тремя длинными колонками.
.magazine {
column-count: 3;
}
@supports (column-wrap: wrap) {
.magazine {
column-height: 18em;
row-gap: 2em;
}
}
Новое: карусель на колонках
Ещё одна свежая возможность превращает колонки в карусель без единой строки JavaScript. Идея такая: делаем по одной колонке на ширину контейнера, лишние колонки уходят вправо (то самое поведение, с которым мы боролись выше), а контейнер прокручивается по горизонтали.
Псевдоэлемент ::column позволяет обратиться к каждой колонке — например, чтобы прокрутка останавливалась ровно на ней. А ::column::scroll-marker рисует для каждой колонки точку-переключатель, как у обычного слайдера.
.slider {
height: 16em;
columns: 1;
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-marker-group: after;
}
.slider::column {
scroll-snap-align: center;
}
.slider::column::scroll-marker {
content: "";
width: 12px;
height: 12px;
border: 2px solid #0f766e;
border-radius: 50%;
}
.slider::column::scroll-marker:target-current {
background-color: #0f766e;
}
Разберём по строчкам:
- scroll-snap-type и scroll-snap-align — прокрутка «примагничивается» к колонкам и не останавливается посередине;
- scroll-marker-group: after — браузер создаёт под каруселью контейнер для точек;
- :target-current — псевдокласс точки, чья колонка сейчас на экране.
Сам ::column стилизовать почти нельзя: он принимает только свойства прокрутки вроде scroll-snap-align и scroll-margin. Фон или рамку колонке так не задать.
Поддержка ::column пока тоже только в Chromium:
Хорошая новость в том, что фолбэк получается сам. Браузер, который не знает ::column, просто пропустит эти правила, и останется обычная горизонтальная лента с прокруткой и примагничиванием, только без точек. Это и есть прогрессивное улучшение: новая возможность добавляет удобства, но без неё ничего не ломается.
Итог
- Колонки делят на части поток содержимого, а Grid расставляет отдельные элементы. Текст и однотипные списки — в колонки, сетки с выравниванием по строкам — в Grid.
- column-width (или columns: 16em) даёт адаптивные колонки без медиазапросов. column-count рядом с ним работает как ограничение сверху.
- column-gap задаёт промежуток, column-rule рисует в нём линию и места не занимает.
- column-span: all растягивает заголовок на все колонки, column-fill: auto имеет смысл только при заданной высоте.
- break-inside: avoid — обязательный спутник карточек, цитат и картинок внутри колонок.
- Порядок в колонках идёт сверху вниз. Для кладки это удобно, для ленты по дате — нет.
- column-height с column-wrap и карусель на ::column пока работают только в Chromium, поэтому подключаем их как улучшение поверх обычных колонок.
Комментарии (0)