Предисловие: при написании CSS-стилей, необходимо сразу учитывать, что контент страницы может быть динамическим, чтобы не возникла ситуация, где мы добавили чуть больше текста, или уменьшили ширину экрана, и вёрстка поплыла. Я наткнулся на хорошую англоязычную статью с примерами универсальных CSS-стилей для часто встречающихся ситуаций, и решил, что эта ценная информация для начинающих верстальщиков должна быть и на русском языке. Так появился этот перевод.
Оглавление:
(кликабельно)
Умный flex-контейнер
CSS flexbox — одна из самых полезных функций компоновки CSS на сегодняшний день. Очень удобно добавить display: flex
в оболочку и расположить дочерние элементы рядом друг с другом.
Однако, когда места недостаточно, эти дочерние элементы по умолчанию не переносятся на новую строку. Нам нужно изменить это поведение с помощью flex-wrap: wrap
Вот типичный пример. У нас есть группа параметров, которые должны отображаться рядом друг с другом.
![.options-list { display: flex; } .options-list { display: flex; }](https://habrastorage.org/getpro/habr/upload_files/922/eea/fdb/922eeafdbddccbba54378b38aa82b3ee.png)
Когда контейнер меньше чем его содержимое, будет отображена горизонтальная прокрутка. Этого следует ожидать, и на самом деле это не является «проблемой».
![](https://habrastorage.org/getpro/habr/upload_files/d88/5b1/a6b/d885b1a6bcee884b93f9e01f0a15262d.png)
Обратите внимание, что элементы все еще рядом друг с другом. Нам нужно чтобы элементы при уменьшении ширины контейнера переносились на другую строку, и не выходили за рамки самого контейнера. Для этого следует написать так:
.options-list {
display: flex;
flex-wrap: wrap;
}
![отлично, теперь выглядит адекватно! отлично, теперь выглядит адекватно!](https://habrastorage.org/getpro/habr/upload_files/6c4/9fb/6d4/6c49fb6d456c177fb339c1219d5f56ee.png)
Расстояние
Мы, верстальщики, должны учитывать разную длину контента. Это означает, что отступы должны быть добавлены к элементу даже если кажется, что они не нужны.
![](https://habrastorage.org/getpro/habr/upload_files/b83/141/414/b83141414de48f9b9eda20f3288ccfe5.png)
В этом примере у нас есть заголовок раздела и кнопка действия справа. Сейчас выглядит нормально. Но давайте посмотрим, что прозойдёт, если заголовок будет длиннее:
![](https://habrastorage.org/getpro/habr/upload_files/1b0/975/75c/1b097575c9eb22f1196742f5c560a779.png)
Заметили, что текст расположен слишком близко к кнопке действия? Возможно, вы думаете о многострочном переносе, но я вернусь к этому в другом разделе. А пока сосредоточимся на отступах.
![](https://habrastorage.org/getpro/habr/upload_files/ef1/f0c/17e/ef1f0c17e295a4b67d3888a7b31f0ba0.png)
.section__title {
margin-right: 1rem; /* Отступ справа */
}
Как обрезать текст точками, читаем ниже:
Длинный контент
Учет длинного контента важен при построении макета. Как вы могли видеть в предыдущем примере, название раздела усекается, если оно слишком длинное. Это необязательно, но для некоторых пользовательских интерфейсов это важно учитывать.
Для меня это "безопасный" подход к CSS. Приятно получить возможность исправить «проблему» до того, как она произойдет.
Вот список имен людей, и сейчас он выглядит идеально.
![](https://habrastorage.org/getpro/habr/upload_files/fa7/4f9/faa/fa74f9faa022fad6235ff62d355aafa8.png)
Однако поскольку это контент, созданный пользователями, нам нужно продумать стили на случай, если что-то будет слишком длинным.
См. следующий пример:
![](https://habrastorage.org/getpro/habr/upload_files/0c0/a34/ba3/0c0a34ba33897de46a26ebb21dc7be4a.png)
Важно, чтобы элементы выглядели однотипно. Для этого мы должны запретить перенос слов, и обрезать часть слов, если они выходят за ширину элемента. Для этого мы напишем:
.username {
white-space: nowrap; /* Запрещаем перенос строк */
overflow: hidden; /* Обрезаем все, что не помещается в область */
text-overflow: ellipsis; /* Добавляем многоточие в конце */
}
![Другое дело! Другое дело!](https://habrastorage.org/getpro/habr/upload_files/5a3/331/76b/5a333176b8a880b25833d636614952dd.png)
Предотвращение искажения изображения
Если изображение не соответствует размерам, которые мы указали в CSS, оно по умолчанию будет сжиматься или растягиваться. Рассмотрим пример - вот карточка еды, в которой изображение соответствует размерам области:
![](https://habrastorage.org/getpro/habr/upload_files/354/216/518/354216518fe7b0a02872adb55d7bb396.png)
Когда пользователь загружает изображение другого размера, оно будет искажено. Это нехорошо! Посмотрите, как растянуто изображение:
![фото искажается, т.к. пытается заполнить всю область карточки целиком фото искажается, т.к. пытается заполнить всю область карточки целиком](https://habrastorage.org/getpro/habr/upload_files/1e0/a5d/a07/1e0a5da071881819d24fa96c679d87ef.png)
Чтобы изображение не искажалось. мы используем object-fit:
.card__image { object-fit: cover; }
На уровне проекта я предпочитаю добавлять object-fit
ко всем изображениям , чтобы избежать неожиданных результатов изображения.
img { object-fit: cover; }
Обратите внимание, что изображение заполняет собой всю область img, а те части изображения которые не умещаются, мы не увидим.
Блокировка цепочки прокрутки
Вы когда-нибудь открывали модальное окно и начинали скроллить, а затем, когда вы доходите до конца и продолжаете прокручивать, содержимое под модальным окном (элемент body) будет прокручиваться? Это называется цепочкой прокрутки (scroll chaining).
В последние годы было несколько хаков, чтобы избежать этого, но теперь мы можем сделать это только с помощью CSS, благодаря свойству overscroll-behavior
На следующем рисунке вы видите поведение цепочки прокрутки по умолчанию.
![](https://habrastorage.org/getpro/habr/upload_files/ead/f9d/741/eadf9d741b9ec4df4c0e7468572df197.png)
Чтобы подобной ситуации не возникало, мы добавим следующие стили к любому компоненту, который нуждается в прокрутке (например, компонент чата, мобильное меню и т. д.). Преимущество этих свойств в том, что они никак себя не проявляют, если прокрутка вовсе отсутствует.
.modal__content {
overscroll-behavior-y: contain;
overflow-y: auto;
}
Резервное значение CSS-переменной
Переменные CSS все чаще используются в веб-дизайне. Существует метод, который мы можем применить чтобы вёрстка не поплыла, если значение переменной CSS по какой-то причине было пустым.
Это особенно полезно при передаче значения переменной CSS через JavaScript. Вот пример:
.message__bubble {
max-width: calc(100% - var(--actions-width));
}
Переменная --actions-width
используется внутри calc()
функции, и ее значение исходит из JavaScript. Предположим, что JavaScript по какой-то причине дал сбой, что произойдет? Будет max-width
ошибочно назначено none
.
Мы можем заранее избежать этого добавив резервное значение в конструкциюvar()
.
.message__bubble {
max-width: calc(100% - var(--actions-width, 70px));
}
Таким образом, если переменная не определена, будет использован резервный вариант - 70px
. Этот подход нужно использовать только когда существует вероятность, что переменная может выйти из строя (например, исходящая из Javascript). В противном случае он не нужен.
Использование фиксированной ширины или высоты
Одной из распространенных вещей, которые ломают макет, является использование фиксированной ширины или высоты с элементом, который имеет содержимое разной длины.
Фиксированная высота
Я часто вижу элементы с фиксированной высотой и содержимым, превышающим эту высоту, что приводит к нарушению макета. Не знаете, как это выглядит? Вот.
.block {
height: 350px;
}
![](https://habrastorage.org/getpro/habr/upload_files/5fa/4bf/f84/5fa4bff8494be8d9705ee059729051f1.png)
Чтобы избежать утечки контента из блока, нам нужно использовать min-height
вместо height
:
.block {
min-height: 350px;
}
![Таким образом, если содержимое станет больше, макет не сломается - контейнер станет больше по высоте Таким образом, если содержимое станет больше, макет не сломается - контейнер станет больше по высоте](https://habrastorage.org/getpro/habr/upload_files/8b4/31c/8fd/8b431c8fd9fc1628271334bdcbe7a32d.png)
Фиксированная ширина
Вы когда-нибудь видели кнопку, контент которой расположен слишком близко к левому и правому краям? Это связано с использованием фиксированной ширины.
.button {
width: 100px;
}
Если текст кнопки длиннее 100px
, он будет близко к краям. Если текст слишком длинный, он будет просачиваться из кнопки. Это нехорошо!
![](https://habrastorage.org/getpro/habr/upload_files/c3b/5ab/d36/c3b5abd3643eef8717c2c05a45e8c429.png)
Чтобы исправить это, мы можем просто заменить width
на min-width
.
.button {
min-width: 100px;
}
Отключение повторения фона
Зачастую при использовании большого изображения в качестве фона мы забываем учитывать случай, когда дизайн просматривается на большом экране. Этот фон будет повторяться по умолчанию.
В основном это не будет видно на экране ноутбука, но его можно четко увидеть на больших экранах.
![](https://habrastorage.org/getpro/habr/upload_files/f62/a84/27a/f62a8427ac2a56b79506063b8c6ec3e4.png)
Чтобы заранее избежать такого поведения, обязательно нужно написать, что фон повторяться не должен!
.image {
background-image: url('..');
background-repeat: no-repeat; /* запрет повторения изображения */
}
Вертикальные @media-запросы
Конечно проще сверстать компонент и протестировать его, только меняя ширину браузера. Однако тестирование по высоте браузера может выявить некоторые интересные проблемы.
Вот пример, который я видел несколько раз. У нас есть боковая секция с основным и второстепенным меню. Второстепенное меню должно располагаться в самом низу боковой секции. Разработчик добавил position: sticky
к второстепенному меню, чтобы оно могло прилипать к низу. На первый взгляд всё хорошо:
![](https://habrastorage.org/getpro/habr/upload_files/e03/dfe/1be/e03dfe1bedf65bf02fc54182d064573d.png)
Проблема в том, что если высота браузера будет меньше, все сломается. Обратите внимание, как два меню наползают друг на друга:
![](https://habrastorage.org/getpro/habr/upload_files/bd9/163/a74/bd9163a74412afd93d29d5195fef584c.png)
Используя вертикальные медиа-запросы CSS, мы можем избежать этой проблемы.
@media (min-height: 600px) {
.secondary_menu {
position: sticky;
bottom: 0;
}
}
Таким образом, второстепенное меню будет привязано к нижней части только в том случае, если высота области просмотра больше или равна 600px . Гораздо лучше, правда?
Вероятно, есть лучшие способы реализации такого поведения (например, использование margin-auto
), но в этом примере я сосредоточился на вертикальном запросе.
Использование justify-content: space-between
В flex-контейнере вы можете использовать justify-content
для разделения дочерних элементов друг от друга. При определенном количестве дочерних элементов макет будет выглядеть нормально. Однако, когда они увеличиваются или уменьшаются, макет будет выглядеть странно.
Рассмотрим следующий пример:
![](https://habrastorage.org/getpro/habr/upload_files/314/e0f/065/314e0f065bb4cc41ec7ab39037089521.png)
У нас есть гибкий контейнер с четырьмя элементами. Расстояние между каждым элементом не является следствием использованияgap
или margin
, оно существует, потому что в контейнере есть justify-content: space-between
.
.wrapper {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
Когда количество элементов меньше четырех, вот что произойдет:
![](https://habrastorage.org/getpro/habr/upload_files/96f/9f3/977/96f9f3977a0406ff79932903c1e1d1d8.png)
Это нехорошо. У данной проблемы есть несколько разных решений
Margin
Flexbox gap (используйте с осторожностью)
Padding (может применяться к родительскому элементу каждого дочернего элемента)
Добавление пустых элементов в качестве разделителя
Для простоты я буду использовать gap
:
.wrapper {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
![](https://habrastorage.org/getpro/habr/upload_files/212/233/91f/21223391fe1a061eec4ada85657f055c.png)
Текст поверх изображений
При использовании текста поверх изображения важно учитывать случай, когда изображение не загружается. Как будет выглядеть текст?
Вот пример:
![](https://habrastorage.org/getpro/habr/upload_files/a26/034/3f1/a260343f178acbf3d797f28fd2399f12.png)
Текст выглядит читаемым, но когда изображение не загружается, белый текст теряется на фоне белой страницы:
![](https://habrastorage.org/getpro/habr/upload_files/6c8/931/b9d/6c8931b9d45527471031545583c3111e.png)
Мы легко исправим это, добавив фоновый цвет к <img>
элементу. Этот фон будет виден только в том случае, если изображение не загрузится. Разве это не круто?
.card__img {
background-color: grey;
}
![](https://habrastorage.org/getpro/habr/upload_files/86c/6e1/d6d/86c6e1d6db0785f9259dacee2d4a5cf6.png)
Будьте осторожны с фиксированными значениями в CSS-Grid
Скажем, у нас есть сетка, которая содержит боковую секцию и основную. CSS выглядит так:
.wrapper {
display: grid;
grid-template-columns: 250px 1fr;
gap: 1rem;
}
Вёрстка поплывёт при небольших размерах окна просмотра из-за нехватки места. Чтобы избежать такой проблемы, всегда используйте медиа-запрос при использовании сетки CSS, как на примере ниже:
@media (min-width: 600px) {
.wrapper {
display: grid;
grid-template-columns: 250px 1fr;
gap: 1rem;
}
}
Показываем полосу прокрутки только когда это необходимо
К счастью, мы можем управлять отображением полосы прокрутки или ее отсутствием не только в случае наличия длинного контента. Настоятельно рекомендуется использовать auto
в качестве значения для overflow
.
Рассмотрим следующий пример:
![](https://habrastorage.org/getpro/habr/upload_files/780/30d/fc0/78030dfc03195d1b36eb846c2104a3a7.png)
Обратите внимание, что даже если содержимое короткое, полоса прокрутки видна. Это не хорошо для пользовательского интерфейса. Как дизайнер, я просто сбиваюсь с толку, видя полосу прокрутки, когда она не нужна.
При overflow-y: auto
полоса прокрутки будет видна только в том случае, если содержимое длинное. Иначе её там не будет:
.element {
overflow-y: auto;
}
![](https://habrastorage.org/getpro/habr/upload_files/f4b/427/a7c/f4b427a7c45f9b3bdd489e46f604fffe.png)
Ширина контента с полосой прокрутки
Еще одна вещь, связанная с прокруткой - её ширина. Взяв предыдущий пример, когда содержимое становится длиннее, добавление полосы прокрутки вызовет сдвиг макета. Причина смены макета заключается в том, чтобы зарезервировать место для полосы прокрутки.
Рассмотрим следующий пример:
![](https://habrastorage.org/getpro/habr/upload_files/2e6/b44/c9e/2e6b44c9ef17f3cf001232f82e7fd713.jpg)
Обратите внимание, как сдвинулось в ширину содержимое, в результате добавления полосы прокрутки. Мы можем зафиксировать ширину контента, чтобы скроллбар её не менял. Для этого напишем стили:
.element {
scrollbar-gutter: stable;
}
![](https://habrastorage.org/getpro/habr/upload_files/6b4/cfe/6c4/6b4cfe6c441355fa928eee2373c1ce3f.jpg)
Минимальный размер контента в CSS-Flexbox
Если во flex-контейнере есть текстовый элемент или изображение, длина которого превышает длину самого элемента, браузер не будет их уменьшать. Это поведение по умолчанию для flexbox.
Рассмотрим следующий пример:
.card {
display: flex;
}
Если в заголовке очень длинное слово, оно не будет переноситься на новую строку.
![](https://habrastorage.org/getpro/habr/upload_files/a30/62e/fef/a3062efef42ba2b752dc95b4b949fcd2.jpg)
Даже если мы используем overflow-wrap: break-word
, это не сработает.
Чтобы изменить это поведение, нам нужно установить min-width
дочернего блока на 0
, из-за того, что значение по умолчаниюmin-width
равно auto
.
.card__title {
overflow-wrap: break-word;
min-width: 0;
}
То же самое относится и к flex-контейнеру, но вместо min-width: 0
мы будем использовать min-height: 0
Результат:
![](https://habrastorage.org/getpro/habr/upload_files/6ed/ae8/e4b/6edae8e4b3be131f9c2db00d92e77842.jpg)
Минимальный размер содержимого в CSS-Grid
Подобно flexbox, CSS-сетка имеет минимальный размер содержимого по умолчанию для своих дочерних элементов, который равенauto
. Это означает, что если есть элемент, который больше, чем элемент сетки, сетка переполнится.
![](https://habrastorage.org/getpro/habr/upload_files/682/ec0/8a7/682ec08a7dd3de19d06ac6492da8a318.png)
В приведенном выше примере у нас есть карусель в основном разделе. Для контекста, вот HTML и CSS примера:
<div class="wrapper">
<main>
<section class="carousel"></section>
</main>
<aside></aside>
</div>
@media (min-width: 1020px) {
.wrapper {
display: grid;
grid-template-columns: 1fr 248px;
grid-gap: 40px;
}
}
.carousel {
display: flex;
overflow-x: auto;
}
Поскольку карусель представляет собой flex-контейнер, который не переносит собственное содержимое на другую строку, её ширина больше, чем секция в которой она находится, в результате этого присутствует горизонтальная прокрутка.
Чтобы исправить это, у нас есть три разных решения:
Использование
minmax()
Применение
min-width
к элементу сеткиДобавление
overflow: hidden
элемента в сетку
В качестве "безопасного" CSS я бы выбрал вариант который использует функцию minmax()
@media (min-width: 1020px) {
.wrapper {
display: grid;
grid-template-columns: minmax(0, 1fr) 248px;
grid-gap: 40px;
}
}
![](https://habrastorage.org/getpro/habr/upload_files/3e2/6c0/c4e/3e26c0c4e0573f6ab8e4b78c35db1c1d.png)
Auto Fit против Auto Fill
При использовании функции CSS-Grid minmax()
важно выбрать между использованием параметров auto-fit
или auto-fill
. При неправильном использовании это может привести к неожиданным результатам.
При использовании функции minmax()
параметрauto-fit
расширяет элементы сетки, чтобы заполнить доступное пространство. В свою очередьauto-fill
сохранит доступное пространство зарезервированным, без изменения ширины элементов сетки. Наглядный пример:
![](https://habrastorage.org/getpro/habr/upload_files/002/e87/2e1/002e872e1c12d6ba56715c26090874c2.png)
При этом, использование auto-fit
может привести к тому, что элементы сетки будут слишком широкими, особенно если они меньше ожидаемого. Рассмотрим следующий пример.
.wrapper {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
grid-gap: 1rem;
}
Если используется только один элемент сетки auto-fit
, он будет расширяться, чтобы заполнить ширину контейнера:
![](https://habrastorage.org/getpro/habr/upload_files/2ab/147/6f0/2ab1476f0ea04afe46b7d75c06400885.png)
В большинстве случаев такое поведение не требуется, поэтому лучше использовать auto-fill
.wrapper {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
grid-gap: 1rem;
}
![](https://habrastorage.org/getpro/habr/upload_files/c9d/1cd/3c3/c9d1cd3c33bb012ad1626bc6e96b1243.png)
Максимальная ширина IMG
Не забудьте установить max-width: 100%
для всех изображений, чтобы они не вылезали за пределы блоков, в которых находятся. Это можно добавить к предыдущим глобальным стилям для img, вот что получится:
img {
max-width: 100%;
object-fit: cover;
}
Использование position: sticky в CSS-Grid
Вы когда-нибудь пробовали использовать position: sticky
с дочерним элементом grid-контейнера? По умолчанию, элементы grid растягиваются. В результате боковой элемент в приведенном ниже примере равен высоте основной секции.
![](https://habrastorage.org/getpro/habr/upload_files/457/30a/473/45730a473dbb0d627eaa66ac693ae79b.jpg)
Чтобы заставить его работать должным образом, вам нужно сбросить свойство align-self
aside {
align-self: start;
position: sticky;
top: 1rem;
}
![](https://habrastorage.org/getpro/habr/upload_files/33e/93d/73a/33e93d73a17f39c17f85f06fd50ecc7f.jpg)
Группировка селекторов для разных браузеров
Не рекомендуется группировать селекторы, предназначенные для работы с разными браузерами. Например, для стилизации заполнителя ввода требуется несколько селекторов для каждого браузера. Если мы сгруппируем селекторы, согласно w3c все правило будет недействительным.
/* Не делайте так, пожалуйста! */
input::-webkit-input-placeholder,
input:-moz-placeholder {
color: #222;
}
Вместо этого напишите так:
input::-webkit-input-placeholder {
color: #222;
}
input:-moz-placeholder {
color: #222;
}
Это список безопасных техник CSS, которые я постоянно использую в своих проектах.
Спасибо Ахмаду Шадиду за качественный контент!
(P.S.: Во многих местах я перевёл своими словами, более доступно для русскоговорящего читателя.)
От переводчика: подытоживая текст статьи, и многолетний опыт вёрстки, хочу дать совет читателям верстальщикам: когда верстаете, всегда думайте о том, какие ситуации могут возникнуть при использовании вашей вёрстки. Что будет, если текста будет сильно больше или сильно меньше. Как элементы себя поведут на устройствах с разной шириной и высотой экранов (кстати для тестирования последнего рекомендую бесплатную программу Responsively). Старайтесь избегать дублирования кода - создавайте общие css-классы для часто используемых стилей или наборов стилей. Используйте препроцессоры (SCSS например), если ещё не использовали их, поверьте, это очень удобно. И самое главное - наслаждайтесь процессом!