Колонки одинаковой высоты для адаптивного дизайна
11-01-2013Время чтения ~ 6 мин.CSS, HTML, LESS, SASS 56879
Проблема создания колонок одинаковой высоты достаточно известна. Суть её в том, что при вёрстке float-блоки колонок имеют разные высоты, согласно своему содержимому.
В идеале же, нужно получить равные колонки
Существуют различные способы добиться этого эффекта. Все они без особого труда находятся в гугле, но в основном методика заключается в иммитации колонок («псевдоколонки»). Делается это за счёт различных смещений (margin и padding), дополнительные контейнеры, которые «подставляются» под настоящие блоки, фоновые картинки и даже за счёт border.
Всё это конечно интересно, но при создании адаптивного дизайна ни один из этих методов не работает как следует.
Ситуация совсем оказалась печальной, когда я стал работать с «полурезиновой» адаптивной сеткой, где меняется значение float блоков (left и none), а высота блоков должна меняться к auto при float:none
.
Пробившись с этой задачей несколько дней, я пришёл к выводу, что легальных и достаточно простых способов её решить всего ничего:
- использовать таблицы (table)
- использовать display: table (и его производные)
- использовать javascript
Верстку через тэги table рассматривать нет смысла. Вариант с display: table интересен, но только до момента, пока не сталкиваешься с «особенностью» браузеров отображать вложенные блоки вроде PRE на всю ширину браузера, а не table-cell-контейнера. В итоге этот вариант так же пришлось отмести, поскольку все эти мелкие «особенности»-баги перевесили все достоинства метода.
В итоге остался только один вариант - использование js-скрипта, который будет автоматически рассчитывать и выставлять высоты колонок с учетом их «адаптивности».
HTML-разметка
Для демонстрации я буду использовать вот такой html-код:
<!DOCTYPE HTML> <html><head> <meta charset="UTF-8"> <title>Две колонки</title> ... тут стили и js-скрипты ... </head> <body> <div class="main"><div class="main-wrap"> <div class="content"><div class="content-wrap"> <h2>content</h2> </div></div> <div class="sidebar1"><div class="sidebar1-wrap"> <h2>sidebar1</h2> </div></div> <div class="clearfix"></div> </div></div> </body></html>
Это достаточно типовая схема модульной сетки на две колонки:
- общий контейнер main
- два блока-колонки content и sidebar1
- каждый блок сопровождается своим wrap-блоком
Строго говоря модульная сетка может быть любой, при этом не имеет значения ни её размеры, ни расположение блоков колонок. Это принципиальное отличие от других существующих js-скриптов, где требуется соблюсти определённую вложенность div-блоков. В моём варианте это не играет никакой роли.
Как сделать адаптивность
Существует несколько методик. Самый простой - это «резиновый» дизайн - когда все колонки задаются в процентах. Проблема такого дизайна в широких мониторах. При ширине более 1000-1200px абзацы превращаются в одиночные строчки, что ухудшает читабельность текста. Поэтому обычно ширину сайта ограничивают до 1000px (960, 980px), что позволяет без проблем разместить текст и боковую колонку.
При фиксированной ширине сайта адаптивность осуществляется путём отдельных @media-правил под каждый диапазон разрешений. Достаточно распространён следующий вариант:
@media (min-width: 1200px) { } @media (min-width: 768px) and (max-width: 959px) { } @media (max-width: 767px) { }
То есть сайт верстается с неким базовым разрешением, например 960px. Если разрешение экрана больше 1200px, то прописываются стили, где ширина контента и сайдбара становится больше. Иногда добавляют ещё одну колонку или какие-то блоки.
Если же разрешение ниже базового, то размеры колонок уменьшают. Как только экран становится совсем узким, чтобы колонки отображались в читабельном виде, их размещают уже одна под другой. Обычно для этого достаточно указать
width: 100%; float: none;
То есть растягиваем на всю ширину и убираем «плавание».
«Полурезиновый» дизайн
Вариант с множеством @media хорош, но несколько трудозатратен. Если подумать, то колонки изначально должны быть резиновыми до какой-то определённой базовой ширины. Если размер экрана более 960px, то ширина сайта не меняется и составляет 960px. Но как только размер уменьшился, то размеры колонок меняются пропорционально друг другу. Как только достигается минимум (совсем узкая колонка), убираем «плавание» и растягиваем блоки на всю ширину.
На демо-примере используется именно такой вариант верстки. Покажу как это делается.
Вначале задаётся общая ширина контейнера:
div.main { margin: 0 auto; width: 100%; max-width: 960px; }
Первое правило центрирует блок в браузере. Второе - выставляет ширину 100%. Третье ограничивает размеры максимальной шириной. Таким образом размер блока не превысит 960px.
Внутренние блоки позиционируются так:
div.content { width: 75%; float: right; } div.sidebar1 { width: 25%; float: left; }
Суммарная ширина блоков должна быть равна 100%. Выравнивание float также произвольно. В этом примере контент располагается справа. Чтобы переместить его влево (то есть поменять колонки) достаточно у div.content указать float: left;
.
Теперь зададим @media правило для узких экранов (менее 767px):
@media (max-width: 767px) { div.content { width: 100%; float: none; } div.sidebar1 { width: 100%; float: none; } }
Сразу покажу как рассчитать точные размеры колонок из px в %. Код на less, кто им не пользуется могут воспользоваться обычным калькулятором.
// вся полезная ширина сайта (контент + сайдбар) @CONTENT_WIDTH: 960px; // ширина основного сайдбара @SIDEBAR_WIDTH: 250px; // расчет пропорций % для контента и сайдбара (в сумме 100%) @RATIO_CONTENT: 100% - (100% * @SIDEBAR_WIDTH / @CONTENT_WIDTH); @RATIO_SIDEBAR: 100% - @RATIO_CONTENT;
В css-less используем так:
div.main { ... max-width: @CONTENT_WIDTH; ... } div.content { ... width: @RATIO_CONTENT; ... } div.sidebar1 { ... width: @RATIO_SIDEBAR; ... }
То есть можно указать произвольные размеры (@CONTENT_WIDTH и @SIDEBAR_WIDTH), LESS сам пересчитает их в проценты.
Равная высота колонок
Алгоритм расчет высот достаточно простой: смотрятся два блока, после этого выбирается тот, который больше и второму блоку выставляется его высота. В моём варианте перед этим выставляется height:auto, с тем, чтобы браузер выставил «правильную» высоту исходных блоков. Если этого не сделать, то высота блоков будет только увеличиваться, но не уменьшаться.
Для того, чтобы отслеживать «адаптивность», я придумал использовать свойство float блоков. Если оно равно none, то ширина блоков должна быть auto и менять высоту уже не нужно.
Вот полный код скрипта:
function column2height(e1, e2) { e1.css('height', 'auto'); e2.css('height', 'auto'); if (e1.css('float') != 'none') { height_1 = e1.height(); height_2 = e2.height(); if (height_1 > height_2) { e2.height(height_1); } else { e1.height(height_2); } } } $(function() { e1 = $('div.sidebar1'); e2 = $('div.content'); column2height(e1, e2); $(window).resize(function() { column2height(e1, e2); }); });
То есть всё шаманство в функции column2height(), которая первый раз срабатывает при загрузке документа, а второй - при изменени размеров браузера.
Блоки, которые требуется сравнивать, указываются один раз (чтобы после их не искать). Если требуется выровнять три колонки, то можно переписать column2height() под нужное количество элементов. Там в общем-то только сравнение высот на поиск максимальной.
Достоинства метода
- Работает с любой html-структурой, где используются float-блоки.
- Не требует переверстки.
- Любая ширина колонок.
- Прост для понимания и модификации.
- «Полурезиновая»-адаптивность не требует множества @media-правил.
- Автоматически распознаётся «адаптивность» для рассчёта высоты колонок.
- Используется настоящая высота колонок, а не «псевдоколонки».
- Блоки колонок могут быть произвольно оформлены, включая отступы, поля, границы, фоновые изображения, слошные фоны и т.п.
К минусам можно отнести только требование наличия включенного javascript у посетителя. По современным меркам это уже не критично.
А что, с display: table действительно так много проблем? Мне пока еще не приходилось его особо использовать, поэтому не знаю, какие там подводные камни. По-моему, это был бы самый красивый способ.
Неужели проблема с PRE и другие аналогичные (какие, кстати?) никак не решаются?
Да, не удалось решить. Два момента, которые мне оказались принципиальными.
1. Отображение только когда получено всё содержимое колонки. То есть вначале пустое место, как данные загрузились, мгновенно появляется. Не то чтобы это раздражало, но как-то неуютно... :)
2. PRE разворачивается на всю ширину браузера. То есть такое впечатление, что элемент просто не видит ширину своего родителя. Решается только явным заданием ширины PRE. Сам понимаешь, для «резинки» не годится. А если выставить ширину в %%, то он считает её от всей ширины. В общем фигня какая-то. Насколько я понял из гугла, то это вообще отмечено как баг браузеров...
Кстати, сейчас браузеры немного по-другому формируют блоки с display:table. Строго говоря, даже нельзя так указывать. Достаточно для парных колонок указать display: table-cell и всё. Если указывать table или table-row, то браузер начинает учитывать вложенные DIV.
В принципе интересно с этим разобраться. Только нужно проверять в разных браузерах: FireFox считает и так, и сяк, а Chrome только «по-новому». Указываешь полный комплект table - появляется куча непонятных отступов.
Ну вот смотри, у тебя сейчас на этом сайте прямо в теге PRE используются разные span'ы для подсветки синтаксиса. На моем сайте аналогично, только без использования PRE. Т.е. можно, просто-напросто, отказаться от этого тега, заменив его дивом.
В принципе можно. Но это потребует переделки старых текстов или какие-то новые плагины для автоматизации. Я вооще надеюсь, что браузеры исправят этот глюк или кто-то найдет какой-то css-хак.
Кстати таблица еще не совсем хорошо подходит если нужно сменить расположение расположение колонок. Придется исхитряться, поскольку колонки должны располагаться так, как идут в коде. С float это несколько проще.
А шаблон к макссайту с реализацие этого подхода существует? Или может планируется?
Да, точно, на счет float это серьезный минус.
Стили один в один, как в статье. Что касается js-кода, то код кинуть в js/my.js - он автоматом подключится.
Макс, я нашел лекарство для PRE в display: table-cell:
Все 3 свойства обязательны! margin-right - тут можно поставить любое отрицательное число, главное, чтобы оно было больше ширины сайдбара. Проверил в разных браузерах - везде все окей.
Я знал, что решение существует =)
Супер! Проверил, работает. :) Интересно, как ты вышел на этот хак? Логика в коде совсем уж не очевидная.
Искал по этой проблеме в англоязычном Интернете. Про margin-right нашел здесь - http://stackoverflow.com/questions/6153363/liquid-pre-inside-table-cell (кстати, очень часто мне этот сайт помогает). А остальное уже методом тыка =)
Еще вариант решения http://chikuyonok.ru/2009/06/float-columns/
К сожалению переделать этот вариант под адаптивный дизайн не получилось.