Отзывчивые CSS шаблоны без медиа-запросов.

Источник: Responsive CSS Patterns without Media Queries.
Автор:  Andy Kirk.
Перевод: .

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

Очень часто мы сталкиваемся с ситуациями, когда вносимые в компоновку элементов изменения должны производиться на основе размеров их контейнера, а не вьюпорта. В связи с этой необходимостью и родилась концепция "Element Queries" («элементные запросы»). Однако, элементные запросы как средство разработки пока не доступны, к тому же Мэт Маркус (Mat Marquis) указал на некоторые проблемы этой концепции, переформулировав ее в "Container Queries" («контейнерные запросы»). Но последние, к сожалению, тоже еще далеки от реальности.

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

Свойство flex-wrap из Flexbox.

Когда дело доходит до реагирования на изменения размеров контейнера, flex-wrap может решить множество проблем. Так, к примеру, довольно часто при достаточном свободном пространстве возникает необходимость в отображении нескольких элементов бок о бок, и в вертикальном их размещении, когда горизонтальное пространство сужается. Взгляните на следующий сниппет, демонстрирующий такое поведение:

See the Pen Responsive module — flexbox by SitePoint (@SitePoint) on CodePen.

Для наглядности в первых двух примерах эмитируется ограничение ширины на достаточно больших вьюпортах. В третьем и четвертом созданы аналогичные условия, плюс увеличенный объем контента и смещение изображения в противоположную сторону. Для того, чтобы увидеть эффект необходимо изменить ширину сниппета. Лучше это сделать с развернутым вариантом на CodePen.

Никакой магии здесь нет, обычная гибкая компоновка с участием свойства flex-wrap, но это прекрасно работает. Помимо создания двух колонок, Flexbox, бесспорно, может быть использован для решения множества других задач, но для наглядности я максимально упростил код примера. Основные составляющие этого метода выглядят очень просто:

<div class="container">
<div class="img">…</div>
<div class="content">…</div>
</div>

.container {
display: flex;
flex-wrap: wrap;
flex-direction: row;
}

.container .content,
.container .img {
flex: 1 0 12em;
/* Значение 12em можно изменить, в соответствии
с вашей контрольной точкой. */
}

Правильное понимание свойств flex-grow, flex-shrink и flex-basis является залогом корректного использования этого метода. Для того, чтобы хорошенько разобраться во взаимосвязи между этими свойствами, советую обратиться вот к этой статье о Flexbox.

Метод «великолепная четверка».

Идея создания изменяющего ширину механизма, который работает на основе заданной контрольной точки, с помощью таких средств как width, min-width, max-width и calc (получившая имя "Fab Four" или «великолепная четверка») принадлежит Реми Парментьеру (Remi Parmentier). Изначально нацеленная на представление электронных сообщений в отзывчивой форме, эта техника с легкостью может быть использована для обычных веб-страниц и на самом деле открывает новые возможности в создании самонастраивающихся модулей, что прекрасно продемонстрировано Терри Кобленцом (Thierry Koblentz). Разберем пример:

{
min-width: 50%;
width: calc((25em — 100%) * 1000);
max-width: 100%;
/* 25em — опционное значение вашей контрольной
точки */
}

Ключевым моментом здесь является то, что если значение свойства width представлено в процентной форме, то оно равно соответствующей доле ширины контейнера форматируемого элемента. Далее за дело берется функция calc, которая сравнивает упоминаемое выше значение с установленным значением контрольной точки и затем генерирует очень большое положительное число (если эта ширина меньше контрольного значения), или реально большое отрицательное число (если ширина больше значения контрольной точки), или же ноль в случае, когда сравниваемые величины равны. Большая положительная ширина «подрезается» с помощью max-width, а большая отрицательная или нулевая ширина устанавливается равным значению свойства min-width.

В нашем примере контрольная точка определена как 25em. Тогда если размер шрифта равен 16px, то получаем контрольное значение ширины 400px. При размере контейнера 400px и выше (другими словами больше или равно контрольному значению), ширина пересчитывается в 0 или большое негативное значение соответственно:
(400 - 400) = 0 * 1000 = 0 или
(400 - 401 = -1) * 1000 = -1000
При таких значениях в игру вступает свойство min-width, устанавливая результирующую ширину элемента из нашего примера равную 50%.

В противном случае, если размер контейнера равен 399px и ниже (другими словами меньше контрольного значения), ширина пересчитывается в большое положительное число:
(400 - 399 = 1) * 1000 = 1000,
что приводит к использованию значения свойства max-width в качестве ширины элемента. Применительно к нашему случаю — это 100%. Следующая диаграмма поможет визуально представить описываемый процесс:

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

Смещенное изображение — полное или частичное заполнение ширины.

В этом демонстрационном примере я применил метод «великолепной четверки» совместно со свойством float с целью переключения между полным и половинчатым значением ширины изображения в зависимости от ширины контейнера:

See the Pen Responsive module — float by SitePoint (@SitePoint) on CodePen.

Для наглядности первые два примера демонстрируют случай с ограничением ширины на достаточно больших вьюпортах. Третий и четвертый — аналогичные условия, но с увеличенным объемом контента и смещением изображения вправо. Для того, чтобы увидеть эффект необходимо «поиграть» с шириной сниппета. Лучше это делать в развернутом варианте на CodePen.

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

Смещенное изображение — видимое или невидимое.

Основа заимствована из предыдущего варианта, но здесь я использовал инверсию результата расчета и удалил декларацию min-width с целью создания эффекта присутствия/отсутствия изображения. Это может пригодиться для небольших экранов, когда доступное пространство предпочтительнее отдать под текстовое содержимое.

See the Pen Responsive module — float / hidden by SitePoint (@SitePoint) on CodePen.

Для наглядности в первых двух примерах эмитируется ограничение ширины на достаточно больших вьюпортах. В третьем и четвертом созданы аналогичные условия, плюс увеличенный объем контента и смещение изображения в противоположную сторону. Для того, чтобы увидеть эффект необходимо изменить ширину сниппета. Лучше это сделать с развернутым вариантом на CodePen.

Проясним ситуацию:

{
/* min-width удалено, так как нам нужна нулевая
ширина при узком экране, и ее отрицательное
значение пересчитывается в ноль */
/* инвертированный множитель: */
width: calc((25em — 100%) * -1000);
max-width: 100%;
/* 25em — опционное значение контрольной точки */
}
Текст и изображение — наложение или последовательное представление.

See the Pen Responsive module — overlaid / stacked by SitePoint (@SitePoint) on CodePen.

Для наглядности первые два примера демонстрируют случай с ограничением ширины на достаточно больших вьюпортах. Как и ранее последние два примера создают аналогичные условия, но с увеличенным объемом контента и смещением изображения вправо. Для того, чтобы увидеть эффект необходимо «поиграть» с шириной сниппета. Лучше это делать в развернутом варианте на CodePen.

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

.pull {
/* Накладываем текст на изображение смещая его
вверх на достаточно большое расстояние: */
margin-bottom: -10em;
}

Ключевой эффект совмещения текста и изображения достигается за счет отрицательных полей, выталкивающих вверх текстовый блок. Однако, такое поведение должно быть отменено когда ширина контейнера пересечет контрольную точку. Сложность данного способа заключается в том, что здесь мы не можем воспользоваться техникой «великолепная четверка», поскольку в природе не существует таких свойств как min/max-margin.

И тем не менее, нет худа без добра. Если внутренний отступ элемента задан в процентах, то его значение вычисляется относительно ширины контейнера, а контроль высоты можно осуществлять с помощью вертикальных отступов padding-top и -bottom. Исходя из этого, с помощью функции calc мы сможем генерировать необходимое нам значение нижнего внутреннего отступа, находящееся в диапазоне между нулем и «очень большим» числом, которое зависит от ширины контейнера:

padding-bottom: calc((30em — 100%) * 1000);

Еще один момент — применение этого правила непосредственно к содержащему текст элементу (с классом .pull) ничего не даст, поскольку мы не сможем корректировать генерируемые этой функцией значения по причине отсутствия требуемых в данном случае свойств min/max-padding. Проблему можно решить путем создания псевдо-элемента, в рамках которого и будут производиться манипуляции внутренними отступами, приводящие к необходимым изменениям высоты. Далее, посредством свойства max-height мы ограничиваем значение высоты содержащего текст элемента до величины, соответствующей отрицательному полю, компенсируя тем самым его появление.

.pull {
/* сдвигом текста на значительное расстояние,
помещаем его поверх изображения: */
margin-bottom: -10em;
/* компенсируем величину сдвига, предотвращая
тем самым увеличение высоты элемента: */
max-height: 10em;
/* и на всякий случай прячем все, что может
выйти за рамки элемента: */
overflow: hidden;
}

.pull::before {
content: "";
display: block;
padding-bottom: calc((30em — 100%) * 1000);
/* вместо 30em вы можете вставить свое значение
контрольной точки */
}

Эффект наложения градиента при совмещении текста и изображения достигается путем применения описанного выше «правила-выключателя» к псевдо-элементу, который, собственно, и содержит фоновый градиент:

.image::after {
content: "";
display: block;
position: absolute;
left: 0;
top: 0;

/* задаем градиент, повышающий разборчивость текста: */
background-image: linear-gradient(to bottom, rgba(0,20,30,0) 0%,rgba(0,20,30,0) 50%,rgba(0,20,30,1) 100%);

/* дополнительные .5% нужны для предотвращения
укорачивания градиента, вызванного дефектами
округления вычислений: */
height: 100.5%;
/* включение наложения градиента при «срабатывании» той же
контрольной точки, которая задействована в
содержащем текст элементе: */
width: calc((30em — 100%) * -1000);
/* 30em — опционное значение контрольной точки */
max-width: 100%;
}

Усеченный список.

Последний разработанный мной метод в своей основе содержит идею навигации Priority+, представленную на CSS-tricks. Хотя в отличие от оригинала, этот вариант несколько облегчен, поскольку не содержит JavaScript:

See the Pen Truncating List by SitePoint (@SitePoint) on CodePen.

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

Еще хочу добавить, что здесь тоже использована техника «великая четверка», но на этот раз она основана на высоте контейнера, а не на его ширине.

<div class="outer">
<div class="inner">
<div class="item">…</div>

<div class="control">…</div>
</div>
</div>
.outer {
height: 2.25em;
overflow: hidden;
}

.outer:target {
height: auto;
}

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

.inner {
display: flex;
flex-wrap: wrap;
}

Внутренний контейнер создает гибкую компоновку со свойством flex-wrap, определяющим многострочное размещение элементов. Таким образом, если происходит перенос какого-либо дочернего элемента на следующую строку, то это приводит к увеличению высоты. Эффект усечения получаем за счет установленного для внешнего .outer контейнера свойства overflow:hidden, благодаря чему все, находящееся ниже верхней строки, будет скрыто от глаз пользователя.

.control {
height: calc((2.25em — 100%) * -1000);
max-height: 2.25em;
}

:target .control—open {
display: none;
}

:target .control—close {
display: block;
}

Переключатели "more/less" («больше/меньше») становятся видимыми только если высота контейнера превышает значение контрольной точки (которое эквивалентно высоте ссылки главного меню), а используемое совместно с :target имя control-класса определяет какое именно из этих двух действий доступно в определенный момент.

Аккуратное выравнивание текста в CSS.

See the Pen Responsive Text Align by SitePoint (@SitePoint) on CodePen.

Отличный прием, которому следует научиться, заключается в оптимальном варианте выравнивания текста, который зависит от свободного пространства контейнера и объема самого текста. Если места достаточно, то текст центруется, в противном случае — смещается влево. Такое поведение достигается очень просто благодаря технике, созданной Виджеем Шармой (Vijay Sharma).

Бонус: хак "Flex-grow 9999".

Ловкий прием известный как Flex-grow 9999 хак, автором которого является Йорен Ван Хи (Joren Van Hee), превосходно вписывается в эту коллекцию.

Спасибо Василису ван Гемерту и его "Look, No Media Queries".

Стоит отметить, что именно презентация Василиса ван Гемерта (Vsilis van Gemert) "Look, No Media Queries" («Смотрите, никаких медиа-запросов.») дала мне стимул к более глубокому исследованию отзывчивого дизайна, а именно к его созданию в обход использования медиа-запросов, что в свою очередь привело меня к написанию данной статьи. То, что он говорит, на самом деле достойно внимания и включает в себя некоторые другие, не менее полезные идеи, не смотря на то, что они выходят за рамки темы, затрагиваемой этой статьей.

Заключение.

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

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

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *