Адаптивная вёрстка

1. Отзывчивый веб-дизайн
Введение
В наши дни трудно найти человека, который не владеет мобильным устройством, подключенным к Интернету. Это планшеты, смартфоны, и даже умные часы. Интернет рос очень быстро, и в какой-то момент встал вопрос, как создавать веб-сайты, подходящие для всех пользователей.
Отзывчивый веб-дизайн является практикой создания веб-сайта, подходящего для работы на любом устройстве с любым размером и разрешением экрана. Сайт должен хорошо выглядеть, быть удобным и сохранять весь свой функционал как на десктопе, так и на мобильном устройстве.
Часто различают два понятия: адаптивный и отзывчивый веб-дизайн. Адаптивный подразумевает под собой вёрстку, при которой веб-страница приспосабливается к ситуации, отображая подходящую сетку. Отзывчивый же плавно меняется в зависимости от различных факторов, таких как ширина или высота области просмотра.
Оба понятия сливаются и смешиваются друг с другом, позволяя создавать наиболее гибкое поведение веб-страницы, поэтому мы не будем делать между ними какого-либо различия.
Фиксированная сетка
Фиксированная сетка создаётся элементами, размер которых задан в абсолютных единицах, например, в пикселях. Как правило, она выравнивается по центру. На широких экранах у нас всё хорошо:

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

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

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

Сетка может перестраиваться и автоматически переносить элементы на новую строку. Как мы уже знаем, есть несколько вариантов это сделать:
- плавающие элементы и свойство
float
(не рекомендуется для построения сеток); - Flexbox Layout и свойство
flex-wrap
; - Grid Layout и свойства, автоматически распределяющие элементы по колонкам, например
auto-fill
иauto-fit
.
Но что делать, если обычного переноса элементов на новую строку недостаточно и необходимо полностью перестроить сетку или даже изменить внешний вид одного и того же элемента в зависимости от каких-либо условий?
2. Медиавыражения
Медиавыражения позволяют адаптировать страницу и применять разные CSS-правила в зависимости от различных условий и даже для различных типов устройств.
Медиавыражения указываются в CSS с помощью ключевого слова @media
.
Выражения для типов устройств
Следующее CSS-правило будет действовать только для принтеров:
@media print {
.element {}
}
А это правило будет действовать как для экранов, так и для принтеров:
@media screen, print {
.element {}
}
Типов устройств довольно много, но на данный момент в соответствии со спецификацией актуальны лишь три типа:
all
— подходит для всех типов устройств, является значением по умолчанию;print
— cоответствует принтерам и устройствам, предназначенным для воспроизведения печатного изображения, например, веб-браузеру, показывающему документ в режиме «Предварительный просмотр».screen
— предназначен для экранов, по факту соответствуют всем вариантам, неподходящим под типprint
.
Остальные типы, хоть и валидны, но устарели и не вызовут соответствия.
Узконаправленные медиавыражения
Мы можем применять CSS-правила в зависимости от различных условий. Рассмотрим самые важные для нас с точки зрения построения адаптивного дизайна.
Правило для экранов с горизонтальной ориентацией:
@media (orientation: landscape) {
.element {}
}
Правило для экранов с вертикальной ориентацией:
@media (orientation: portrait) {
.element {}
}
Самыми же полезными для нас будут медиавыражения, реагирующие на размер области просмотра — ширину или высоту:
/* Стили по умолчанию */
.element {
color: black;
}
/* Точная ширина */
@media (width: 375px) {
.element {
color: red;
}
}
/* Минимальная ширина */
@media (min-width: 768px) {
.element {
background: yellow;
}
}
/* Максимальная ширина */
@media (max-width: 1024px) {
.element {
border: 2px solid blue;
}
}
Комплексные медиавыражения
Иногда одного условия может быть недостаточно. В таком случае применяются логические операторы: and
, not
и only
. Кроме того, можно объединять несколько медиавыражений в список через запятую, в этом случае будет проверяться соответствие любому из них.
Оба условия должны быть выполнены:
@media (min-width: 30em) and (orientation: landscape) {
.element {}
}
Вьюпорт должен соответствовать минимальной высоте или иметь вертикальную ориентацию экрана:
@media (min-height: 680px), (orientation: portrait) {
.element {}
}
Ключевое слово not
инвертирует смысл конкретного медиа-запроса, но не будет применяться к каждому запросу, которые разделёны через запятую в списке.
Следующие два медиавыражения эквивалентны:
@media not print and (color) {
.element {}
}
@media not (print and (color)) {
.element {}
}
Ключевое слово only
не позволяет старым браузерам, которые не поддерживают медиа-запросы, применять заданные стили, оно никак не влияет на современные браузеры.
Данное правило сработает только в современных браузерах, поддерживающих стандарт HTML5, и при ширине вьюпорта не менее 768px
:
@media only screen and (min-width : 768px) {
.element {}
}
Подключение отдельных CSS-файлов
Мы можем не только писать CSS-код с медиавыражениями, но и подключать разные CSS-файлы в зависимости от условий:
<!-- Стили для десктопа -->
<link href="styles.css" rel="stylesheet" media="(min-width: 1025px)">
<!-- Стили для мобильный устройств -->
<link href="styles-mobile.css" rel="stylesheet" media="(max-width: 1024px)">
3. Viewport и масштабирование
Область просмотра
Viewport веб-браузера — это видимая пользователю область веб-страницы, которую может увидеть пользователь, не прибегая к прокрутке.

Размеры этой области в первую очередь определяются размером экрана устройства. Viewport смартфонов относительно небольшой — в среднем 4-7 дюймов, в то время как диагональ мониторов персональных компьютеров составляет в среднем от 15 до 27 дюймов, то есть и область просмотра намного больше.
Адаптивная сетка и медиавыражения позволили создавать гибкие веб-интерфейсы. Но и этого оказалось недостаточно. Технологии развиваются очень быстро, и с появлением экранов с высокой плотностью пикселей, обладающих высоким разрешением, мы получили новую проблему.
Давайте в качестве примера рассмотрим два уже давно устаревших мобильных устройства: Apple iPhone 3 и iPnone 4. Оба устройства имеют одинаковую диагональ — 3.5 дюйма. То есть физические размеры экранов данных смартфонов одинаковы.

Разрешение экрана iPhone 3 составляет 320*480px
, его плотность пикселей — 163ppi
. Однако для iPhone 4 те же параметры составляют 640x960px
и 326ppi
соответственно.
Характеристики экрана iPhone 3 в целом сопоставимы с характеристиками обычного монитора настольного компьютера. А это означает, что все элементы, отображаемые на экране, будут иметь схожий размер, в том числе и шрифты.
На iPhone 4 же плотность пикселей в два раза больше при той же диагонали, а значит все элементы, размеры которых заданы в абсолютных единицах, станут в два раза меньше по ширине и высоте. Размер шрифтов также уменьшится в двое, то есть шрифт размером 16px
будет выглядеть как 8px
. Такая страница будет трудночитаемой.
Но и на iPhone 3, и на iPhone 4 главная страница нашего учебного проекта будет выглядеть одинаково:

У устройств появилось две характеристики вместо одной — физическое разрешение и разрешение области просмотра при нормальном масштабировании. Единицей измерения является независимый CSS-пиксель (device-independent pixel, dip).
Cоответствие между физическими пикселями (dp) и независимыми от устройства пикселями называется соотношением пикселей устройств (device pixel ratio, dpr). Определяется по формуле:
dpr = dp / dip
Обычно зависимость между плотностью пикселей и dpr выглядит так:
Плотность пикселей | device pixel ratio |
меньше 200 ppi | 1 |
200 — 300 ppi | 1.5 |
больше 300 ppi | ppi / 150 c округлением до 0.5 |
С развитием технологий экраны с высокой плотностью пикселей пришли и на десктоп. Первыми снова стали Apple, выпустившими всем известные экраны с технологией Retina. Затем подтянулись и многие другие производители. Экраны с высоким разрешением стали массовыми.
Характеристики экранов различных популярных устройств можно найти, например, на данном ресурсе.
Метатег viewport
Для решения проблемы компанией Apple был разработан метатег viewport, позволяющий указать, в каком масштабе необходимо отображать пользователю видимую область страницы.
Включение поддержки метатега viewport для адаптивных сайтов осуществляется посредством добавления следующей строчки в раздел <head>
веб-страницы:
<meta
name="viewport"
content="width=device-width, initial-scale=1"
>
Однако, чтобы всегда гарантировать правильное поведение нашей вёрстки, мы добавим ещё несколько параметров атрибуту content
:
<meta
name="viewport"
content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"
>
Атрибут name
указывает браузеру, какую именно информацию о странице мы ему сообщаем. В данном случае это viewport.
Далее идёт набор параметров в атрибуте content
:
- параметр (
width=device-width
) отвечает за то, чтобы ширина видимой области веб-страницы соответствовала CSS-ширине устройства (dip
); - параметр (
initial-scale=1
) устанавливает первоначальный масштаб веб-страницы. Значение1
означает, что масштаб равен100%
. - три последующих параметра запрещают пользователю самостоятельно изменять масштаб странички. Наш дизайн полностью адаптивен, а случайное или намеренное масштабирование даст в итоге плохой пользовательский опыт, чего нам хотелось бы избежать.
4. Паттерны проектирования
Мы познакомились со всеми видами сеток и методами адаптивной вёрстки. Осталось определиться, какой подход мы будем использовать при воплощении дизайна в код.
Варианты макетов
Если мы обратимся к макетам, которые предоставляет нам дизайнер, то увидим, что для адаптива всегда создается несколько конкретных вариантов определённой ширины, отвечающих наиболее важным случаям:
- 320px — минимальная ширина, которую можно встретить на смартфонах.
- 768px — ширина вьюпорта большого количества планшетов, например Apple iPad;
- несколько вариантов в диапазоне от
1170px
до1920px
, охватывают большое количество популярных десктоп-экранов;
Всё, что оказывается между макетами, как правило, отдаётся на откуп frontend-разработчику.
Как правильно читать макеты
Обратите внимание, все макеты за исключением самого крупного, ориентированного на fullHD-десктоп или больше, представляют собой воплощение минимального размера своего диапазона:
- макет
320px
предназначен для диапазона320px - 767px
; - макет
768px
предназначен для планшетов и устройств в диапазоне от768px
и до следующего более крупного (в нашем учебном проекте это1340px
, часто можно встретить1170px
); - аналогично для всех возможных последующих более крупных макетов.
Объяснение простое: растянуть сетку никакой проблемы не представляет, в то время как при сжатии мы сразу неизбежно столкнёмся с недостатком свободного места.
Самый широкий макет часто может быть исполнен так, что ориентирован на максимально большой экран, а элементы дизайна занимают всё, или практически всё доступное пространство. В этом случае точка перехода к такому макету от предыдущего выбирается произвольно, исходя из условий. Так, для макета шириной 1920px
точкой перелома может быть 1900px
или меньше. Для нашего учебного проекта хорошо подойдёт точка перелома 1660px
.
Выбор точек перелома
Точки перелома следует выбирать в соответствии с характеристиками вьюпортов самых популярных на рынке устройств. При этом стоит помнить, что настольные версии браузеров при определении указанной в медиа-запросе ширины не учитывают размер своего скроллбара, а значит его нужно обязательно вычесть. В большинстве современных браузеров ширина скроллбара по умолчанию составляет 17 пикселей.
Таким образом для экранов шириной 1368px
хорошим выбором для точки перелома будет значение 1340px
.
Мобильные браузеры накладывают свой скроллбар поверх вьюпорта, для таких устройств точки перелома можно указывать в полном соответствии с размером вьюпорта.
Хорошие точки перелома:
320px
(смартфоны);768px
(Apple iPad);1024px
(Apple iPad Pro);1170px
(десктоп);1340px
(огромное количество ноутбуков с шириной экрана 1368px);1510px
(MacBook Pro 16: 1536px, большое количество десктоп-мониторов шириной 1600px)
Резиновые или фиксированные сетки?
Любая фиксированная сетка это всегда частный случай резиновой сетки.
Преимущества резиновой сетки:
- гибкая вёрстка даёт лучший пользовательский опыт;
- позволяет использовать меньше точек перелома — медиавыражений, зависящих от размера вьюпорта;
- как следствие предыдущего пункта, требует меньше правок при внесении изменений.
Недостатки резиновой сетки:
- более трудоёмкая реализация, так как требует вычислений в относительных единицах;
- большие трудозатраты означают удорожание работы;
Идеальный подход — грамотное сочетание и использование как резиновых, так и фиксированных сеток в зависимости от конкретной ситуации.
Всегда стоит помнить, что ни один дизайнер не может закрыть макетами все возможные варианты, а количество устройств с самыми разными вьюпортами на данный момент огромно. Кроме того никто не мешает пользователю открепить окно от экрана, задав ему произвольный размер. Это значит, что ваша реализация должна хорошо выглядеть и работать на всём протяжении адаптива, от самого маленького до самого большого. А в идеале, необходимо предусмотреть правильное поведение ещё и на широкоформатных мониторах с нестандартными соотношениями сторон.
Mobile first или desktop first?
С какого макета начать вёрстку? С десктопа, или же с мобильного варианта? Вопрос достаточно сложный, и очень часто зависит не от frontend-разработчика.
На данный момент хорошим тоном считает подход mobile first:
- используем принцип «от простого к сложному», как правило мобильная вёрстка значительно проще;
- при переходе к более крупным макетам по точкам перелома чаще всего мы просто добавляем новые стили, в то время как при обратном подходе правила придётся перебивать, а значит увеличивается количество CSS-кода;
- меньшее количество кода влечёт лучшую оптимизацию веса страниц и увеличение скорости загрузки.
Если сразу доступны все макеты, и нет требований начать с десктопа, то mobile first — лучший выбор. Однако чаще всего веб-дизайнер начинает проектировать и рисовать макеты именно с десктопа, а разработку приходится начинать параллельно. А бывает и так, что реализацию десктопной версии хочет как можно быстрее увидеть заказчик. Тогда вариантов не остаётся, используем подход desktop first.
Нужно понимать, что преимущества mobile first довольно незначительны, оба подхода имеют право на жизнь.
Пример CSS-кода mobile-first
/* Стили по умолчанию */
.element_1 {}
.element_2 {}
/* Стили для планшетов */
@media (min-width: 768px) {
.element_1 {}
.element_2 {}
}
/* Десктоп-стили */
@media (min-width: 1170px) {
.element_1 {}
.element_2 {}
}
Пример CSS-кода desktop-first
/* Стили по умолчанию */
.element_1 {}
.element_2 {}
/* Стили для планшетов */
@media (max-width: 1169px) {
.element_1 {}
.element_2 {}
}
/* Cтили для смартфонов */
@media (max-width: 767px) {
.element_1 {}
.element_2 {}
}
5. Оптимизация кода
Структура кода в Less
Мы работаем не с чистым CSS, а используем препроцессор Less и блоки БЭМ. Как быть в этом случае?
Есть два подхода:
1. Пишем весь CSS-код БЭМ-блока и его элементов, ниже указываем медиавыражение, а внутри снова БЭМ-блок:
.block {
/* стили блока */
&__element {
/* стили элемента */
}
}
@media (max-width: 767px) {
.block {
/* стили блока */
&__element {
/* стили элемента */
}
}
}
Таким образом мы группируем все стили внутри наших медиавыражений. Проблема здесь в том, что в крупных блоках очень быстро становится неудобно ориентироваться — вам постоянно приходится искать стили того селектора, который вы в данный момент правите.
2. Каждый блок объявлений содержит все медиавыражения внутри себя:
.block {
// стили блока
@media (max-width: 767px) {
// стили блока в адаптиве
}
&__element {
// стили элемента
@media (max-width: 767px) {
// стили элемента в адаптиве
}
}
}
Данный подход позволяет видеть все стили селектора на протяжении всего адаптива в одном месте.
На первый взгляд кажется, что мы добавляем огромное количество медиавыражений, а значит увеличиваем размер скомпилированного CSS-файла. Однако это не так.
Давайте вспомним урок, на котором мы говорили об автоматизации разработки. С помощью Gulp и плагина PostCSS-Sort-Media-Queries мы сортируем и объединяем все медиавыражения, оптимизируя конечный CSS-код.
postcss([
sortMediaQueries({
sort: 'desktop-first'
})
])
Плагин позволяет задать направление сортировки — desktop-first
или mobile-first
, или же создать собственное правило сортировки при необходимости.
Переменные для медиавыражений
Каждый раз прописывать длинные названия медиавыражений очень неудобно, мы можем вынести их в переменные, указав в имеющемся файле variables.less
, пример ниже:
@bw768: ~"(max-width: 767px)
@w768: ~"(min-width: 768px)
@bw1020: ~"(max-width: 1019px)";
@w1020: ~"(min-width: 1020px)";
@bw1340: ~"(max-width: 1339px)";
@w1340: ~"(max-width: 1340px)";
@bw1660: ~"(max-width: 1659px)";
@w1660: ~"(min-width: 1660px)";
Теперь в CSS-правилах мы можем писать так:
.block {
// десктоп стили
@media @bw1660 {
// стили до 1660px
}
@media @bw1340 {
// стили до 1340px
}
@media @bw1020 {
// стили до 1020px
}
@media @bw768 {
// стили до 768px
}
}
Некоторые особенности адаптивной вёрстки
Когда мы пишем CSS-код для мобильных устройств, возникает сразу несколько проблем.
1. При переходе от мобильного дизайна к десктопному и наоборот одни блоки перестраивают свой внешний вид и своё расположение, другие исчезают, третьи — появляются.
Вёрстку следует выполнять так, чтобы избегать дублирования одних и тех же логических блоков на протяжении всего адаптива. К примеру, основное навигационное меню в шапке на десктопе и открывающееся навигационное меню для мобильных устройств должны быть одним и тем же единственным блоком, который меняет свой внешний вид и поведение в зависимости от размеров вьюпорта.
2. На десктопе мы пользуемся различными устройствами ввода, в том числе и мышью, которая легко позволяет взаимодействовать с мельчайшими элементами на странице. Мобильные же устройства создаются под управление пальцем. Следовательно и наша вёрстка должна быть такой, чтобы пользователю было удобно работать со всеми интерактивными элементами (кнопки, ссылки и прочее).
Если кнопка визуально выглядит совсем небольшой, скажем, это небольшая иконка, следует добавить такой кнопке прозрачные поля, слегка увеличив её размер, чтобы пользователь мог легко её нажать.

6. Примеры адаптивной вёрстки
Контейнер
Первое, что стоит вспомнить, в файле mixins.less
у нас есть миксин .container {}
.
Давайте осмотрим все макеты и изучим боковые отступы:
- макет 1340 —
40px
; - макет 768 —
30px
; - макет 320 —
20px
.

Таким образом, стили нашему контейнеру для адаптива мы можем прописать так:
.container {
box-sizing: border-box;
width: 100%;
max-width: 1414px;
margin: 0 auto;
padding-left: 40px;
padding-right: 40px;
@media @bw1340 {
padding-left: 30px;
padding-right: 30px;
}
@media @bw768 {
padding-left: 20px;
padding-right: 20px;
}
}
Если внимательно осмотреть макеты, можно увидеть, что есть более узкие блоки одинаковой ширины, например заголовки секций или кнопки «Я хочу». Создадим ещё один миксин для таких блоков:
.width_624 {
box-sizing: border-box;
width: 100%;
max-width: 624px;
@media @bw1660 {
max-width: 604px;
}
@media @bw1340 {
max-width: 566px;
}
}
В дальнейшем применим его, а пока начнём с шапки.
Шапка сайта
Мы уже сверстали десктоп-версию шапки на одном из предыдущих уроков. Давайте взглянем на следующий макет шириной 1340px
. Изменений совсем немного: высота шапки и размер логотипа. Не забудем изменить и ширину правого блока с кнопкой входа.
Точку перехода выберем произвольно, к примеру 1660px
, Таким образом самый крупный десктопный макет будет отображаться на мониторах с разрешением 1680*1050
, 1920*1080
и любых других, попадающих по ширине в данный диапазон, начиная от 1660px
и выше.
Давайте добавим соответствующие стили:
.page-header {
// десктоп-стили
@media @bw1660 {
height: 111px;
}
&__logo {
// десктоп-стили
@media @bw1660 {
width: 152px;
height: 111px;
}
}
&__right-block {
// десктоп-стили
@media @bw1660 {
width: 152px;
}
}
}
Следующий макет шириной 768px
несёт с собой существенные изменения. Навигационное меню оказалось скрыто, появилась кнопка-гамбургер, а кнопка входа превратилась в иконку:

Однако прежде чем прописать точку перелома в 1340px
для перехода к макету в 768px
, давайте сильнее сожмём окно браузера и посмотрим, как поведёт себя наша вёрстка. Как можно видеть, все элементы шапки прекрасно помещаются даже при ширине вьюпорта в 1000px
, а значить прятать основную навигацию так рано просто неразумно.
Следующая подходящая для нас точка перелома — 1020px
. Наша шапка будет выглядеть одинаково на всем интервале от 1020px
до 1660px
, мы захватим как крупные планшеты (например, Apple Ipad Pro), так и небольшие десктоп-мониторы.
Есть ещё одна проблема: мы задавали фон и его размытие всей шапке, однако на мобильных устройствах у нас имеется раскрывающееся навигационное меню, фон которого также имеет размытие. Меню является вложенным элементом шапки, и в этом случае размытие для него работать не будет. Нам нужно отдельно задать фон для верхней части шапки и меню. Не прибегая к дополнительной разметке, сделать это можно с помощью псевдоэлемента:
.page-header {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 147px;
display: flex;
align-items: center;
justify-content: space-between;
z-index: 100;
@media @bw1660 {
height: 111px;
}
&::before {
content: "";
display: block;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: fade(@gray, 98%);
@supports (backdrop-filter: blur(20px)) {
background-color: fade(@gray, 70%);
backdrop-filter: blur(20px);
}
&__container {
.container;
position: relative;
display: flex;
align-items: center;
z-index: 1;
}
}
Мы убрали фон у самой шапки, добавили его псевдоэлементу, кроме того задали z-index
для контейнера.
Навигационное меню временно скроем, чтобы оно нам не мешало.
.page-header {
&__nav {
display: none;
}
}
В правой части шапки у нас было ограничение по ширине, которое больше не нужно, кроме того сам блок необходимо «прибить» к правому краю экрана:
.page-header {
&__right-block {
// десктоп-стили
@media @bw1020 {
width: auto;
margin-left: auto;
}
}
}
Кнопка входа в мобильном варианте превращается в иконку. Ранее мы использовали БЭМ-блок .btn
, но подобное поведение кнопки в данном случае не является универсальным, поэтому правильно будет убрать данный класс из разметки, а десктоп-стили продублировать в правиле самого БЭМ-элемента шапки. Для адаптива напишем новые стили в соответствии с дизайном.
Далее мы должны экспортировать иконку из Figma, добавить её в SVG-спрайт, написать соответствующую разметку и скрыть данный элемент для десктоп-устройств.
<div class="page-header__right-block">
<a href="#" class="page-header__user-btn">
Войти
<svg class="page-header__user-icon" width="21" height="18">
<use
xlink:href="assets/icons/symbols.svg#user"
></use>
</svg>
</a>
</div>
.page-header {
&__user-btn {
// десктоп-стили
@media @bw1020 {
display: flex;
width: 40px;
height: 40px;
padding: 0;
border: none;
background: none;
color: @black;
font-size: 0;
}
&:hover {
background-color: @black;
color: @white;
@media @bw1020 {
background-color: transparent;
}
}
}
&__user-icon {
display: none;
@media @bw1020 {
display: block;
margin: auto;
}
}
}
На мобильных устройствах для кнопки входа мы обнулили размер шрифта, задали ширину и высоту 40px
, убрали границы и вывели соответствующую иконку.
Осталось добавить кнопку-гамбургер. В данном случае нам не нужно использовать иконки, достаточно чистого CSS-кода. Кроме того в дальнейшем такой подход позволит создать простую анимацию превращения гамбургера в крестик при открытии навигационного меню.
<div class="page-header__right-block">
<a href="#" class="page-header__user-btn">
<!-- кнопка входа -->
</a>
<button type="button" class="page-header__nav-toggle"></button>
</div>
.page-header {
&__nav-toggle {
display: none;
@media @bw1020 {
display: block;
position: relative;
right: -11px;
margin-left: 18px;
width: 40px;
height: 40px;
border: none;
background: none;
}
&::before,
&::after {
content: "";
display: block;
position: absolute;
top: 19px;
left: 11px;
width: 18px;
height: 2px;
border-radius: 2px;
background-color: @black;
transform-origin: 50% 50%;
}
&::before {
top: 13px;
box-shadow: 0 6px 0 @black;
}
&::after {
top: 25px;
}
}
}
Реальный размер кнопки-гамбургера мы задали чуть-больше, чем на макете. Так на неё удобнее нажимать пользователю. Чтобы компенсировать увеличившийся размер, кнопку необходимо визуально сдвинуть вправо, что мы и сделали с помощью CSS-свойства right
.
Перейдём к самому маленькому макету в 320px
. Изменений совсем немного: высота шапки, размеры логотипа и некоторые отступы. Наша точка перелома для данного макета — 768px
.
.page-header {
// предыдущие стили
@media @bw768 {
height: 70px;
}
&__logo {
// предыдущие стили
@media @bw768 {
width: 96px;
height: 70px;
}
}
&__nav-toggle {
// предыдущие стили
@media @bw768 {
margin-left: 5px;
}
}
}
Давайте вспомним, что шапка у нас фиксированная, а для <body>
мы задавали внутренний верхний отступ. Добавим соответствующие стили для адаптива:
.body {
//прочие стили
padding-top: 147px;
@media @bw1660 {
padding-top: 111px;
}
@media @bw768 {
padding-top: 70px;
}
}
Теперь на всём протяжении адаптива шапка сайта должна выглядеть как требуется.
Сверстаем мобильное навигационное меню в открытом виде:
.page-header {
&__nav {
box-sizing: border-box;
display: flex;
align-items: baseline;
margin: 0 auto;
@media @bw1020 {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100vh;
flex-direction: column;
align-items: flex-start;
padding: 211px 30px 30px;
background-color: fade(@gray, 98%);
transition: transform 0.4s;
/*
закончив вёрстку шапки,
раскомментируем следующее свойство,
чтобы скрыть наше меню:
transform: translateX(-100%);
*/
overflow-x: hidden;
overflow-y: auto;
@supports (backdrop-filter: blur(20px)) {
background-color: fade(@gray, 70%);
backdrop-filter: blur(20px);
}
}
}
}
Свойства overflow-x
и overflow-y
позволяют нам избежать случайной горизонтальной прокрутки и, напротив, при необходимости добавить автоматическое появление вертикальной.
Свойство transform
позволяет нам сдвигать, поворачивать, масштабировать, наклонять и совершать прочие действия с элементами. Мы его используем для того, чтобы сдвинуть навигационное меню влево за пределы вьюпорта с помощью значения translateX(-100%)
, то есть на всю ширину элемента.
Благодаря свойству transition
навигационное меню будет открываться плавно в течение 0.4 секунды.
Для ссылок навигации нужно убрать горизонтальные отступы, добавить вертикальные и изменить размер шрифта:
.page-header {
&__nav-link {
// десктоп-стили
@media @bw1020 {
margin: 0 0 40px;
font-size: 16px;
line-height: 22px;
}
}
}
Ниже навигационных ссылок можно увидеть личную ссылку зарегистрированного пользователя и кнопку выхода. На десктопе эти элементы должны быть скрыты, а для мобильных устройств выведены.
Экспортируем из Figma иконку, добавим в SVG-спрайт и дополним разметку:
<nav class="page-header__nav">
<a href="#" class="page-header__nav-link">Мероприятия</a>
<a href="#" class="page-header__nav-link">Блог</a>
<a href="#" class="page-header__nav-link">О нас</a>
<a href="#" class="page-header__nav-link">Контакты</a>
<div class="page-header__nav-user">
<a href="#" class="page-header__nav-user-link">
Александр С.
</a>
<a href="#" class="page-header__nav-logout">
<svg class="page-header__nav-logout-icon" width="24" height="24">
<use
xlink:href="assets/icons/symbols.svg#door"
></use>
</svg>
</a>
</div>
</nav>
.page-header {
&__nav-user {
display: none;
@media @bw1020 {
display: flex;
align-items: center;
margin-top: 20px;
}
}
&__nav-user-link {
margin-right: 25px;
font-weight: 400;
font-size: 16px;
line-height: 22px;
}
&__nav-logout {
width: 24px;
height: 24px;
font-size: 0;
}
}
Последнее, что осталось — задать внутренние отступы для навигационного меню в соответствии с макетом 320px
.
.page-header {
&__nav {
//предыдущие стили
@media @bw768 {
padding: 150px 20px 30px;
}
}
}
Теперь раскомментируем свойство transform
навигационного меню и скроем его таким образом.
Наша шапка практически полностью готова. Остались некоторые мелочи: возможность смены цвета svg-иконки по наведению мыши на кнопку, открытие навигационного меню по клику на кнопку-гамбургер. Доделаем это на следующих уроках.
Заголовок секции
.section-title {
.width_624;
//прочие десктоп-стили
@media @bw1660 {
margin-bottom: 130px;
}
@media @bw1340 {
min-height: 52px;
margin-bottom: 100px;
padding: 11px 40px;
font-size: 24px;
}
@media @bw768 {
min-height: 42px;
margin-bottom: 80px;
padding: 11px 20px;
font-size: 20px;
}
@media @bw600 {
font-size: 16px;
}
}
Здесь была добавлена новая произвольная точка перелома 600px
для смены размера шрифта заголовка исходя исключительно из чувства собственного вкуса и сочетания с прочими заголовками и кнопками.
@bw600: ~"(max-width: 599px)";
@w600: ~"(min-width: 600px)";
Листинг мероприятий
Вновь возвращаемся к листингу мероприятий. Здесь всё совсем просто.
Для начала пробежимся по макетам адаптива, и посмотрим, как меняются внутренние стили карточки мероприятия. Изменений совсем немного, это некоторые отступы и размеры шрифтов:
.event-card {
&__title {
//предыдущие стили
@media @bw1660 {
margin: 0 0 10px;
}
@media @bw1340 {
min-height: 54px;
font-size: 20px;
line-height: 27px;
}
@media @bw768 {
min-height: 44px;
font-size: 16px;
line-height: 22px;
}
}
&__description {
//предыдущие стили
@media @bw1020 {
font-size: 14px;
line-height: 19px;
}
}
}
Размеры и отступы карточек непосредственно в листинге:
.listing {
&__event-card {
//предыдущие стили
@media @bw1660 {
width: calc(33.3333% - 40px);
margin: 0 20px 100px;
}
@media @bw1340 {
width: calc(33.3333% - 30px);
margin: 0 15px 100px;
}
@media @bw1020 {
width: calc(50% - 30px);
max-width: 405px;
margin: 0 15px 80px;
}
@media @bw768 {
margin: 0 10px 80px;
}
@media @bw650 {
width: 100%;
margin: 0 0 60px;
}
}
}
Соответствующие свойства самого листинга:
.listing {
&__events-list {
//предыдущие стили
@media @bw1660 {
margin: 0 -20px -100px;
}
@media @bw1340 {
margin: 0 -15px -100px;
}
@media @bw1020 {
justify-content: center;
margin: 0 -15px -80px;
}
@media @bw768 {
margin: 0 -10px -80px;
}
@media @bw650 {
margin: 0 0 -60px;
}
}
}
Мы добавили ещё одну новую точку перелома 650px
, определяющей минимальную ширину, при которой мы можем разместить две карточки листинга в один ряд:
@bw650: ~"(max-width: 649px)";
@w650: ~"(min-width: 650px)";
В конечном счёте получим следующее:
Форма бронирования стола
Данная форма у нас свёрстана с помощью Grid Layout. Подробно останавливаться не будем, просто перестроим сетку, со всем остальным вы легко справитесь самостоятельно.
.reserve {
display: grid;
gap: 65px 11%;
grid-template-columns: 405px calc(89% - 405px);
grid-template-areas:
"checks scheme"
"order legend";
width: 100%;
@media @bw1340 {
gap: 65px 6.5%;
grid-template-columns: 340px calc(93.5% - 340px);
}
@media @bw1020 {
gap: 60px;
grid-template-columns: auto 390px;
grid-template-areas:
"scheme scheme"
"checks legend"
"checks order";
}
@media @bw768 {
grid-template-columns: 100%;
grid-template-areas:
"scheme"
"legend"
"checks"
"order";
}
}

7. Материалы для самостоятельного изучения
- Медиавыражения:
https://developer.mozilla.org/ru/docs/Web/CSS/Media_Queries/Using_media_queries - Viewport:
https://developer.mozilla.org/ru/docs/Glossary/Viewport - Характеристики экранов некоторых устройств:
https://yesviz.com/devices.php