Безопасный CSS, или как писать универсальные стили

ARTICLES 30.01.22 30.01.22 327
Бесплатные курсына главную сниппетов

Предисловие: при написании CSS-стилей, необходимо сразу учитывать, что контент страницы может быть динамическим, чтобы не возникла ситуация, где мы добавили чуть больше текста, или уменьшили ширину экрана, и вёрстка поплыла. Я наткнулся на хорошую англоязычную статью с примерами универсальных CSS-стилей для часто встречающихся ситуаций, и решил, что эта ценная информация для начинающих верстальщиков должна быть и на русском языке. Так появился этот перевод.

Оглавление:

  1. Умный flex-контейнер

  2. Расстояние

  3. Длинный контент

  4. Предотвращение искажения изображения

  5. Блокировка цепочки прокрутки

  6. Резервное значение CSS-переменной

  7. Использование фиксированной ширины или высоты

  8. Отключение повторения фона

  9. Вертикальные @media-запросы

  10. Использование justify-content: space-between

  11. Текст поверх изображений

  12. Будьте осторожны с фиксированными значениями в CSS-Grid

  13. Показываем полосу прокрутки только когда это необходимо

  14. Ширина контента с полосой прокрутки

  15. Минимальный размер контента в CSS-Flexbox

  16. Минимальный размер содержимого в CSS-Grid

  17. Auto Fit против Auto Fill в CSS-Grid minmax

  18. Максимальная ширина IMG

  19. Использование position: sticky в CSS-Grid

  20. Группировка селекторов для разных браузеров

Умный flex-контейнер

CSS flexbox — одна из самых полезных функций компоновки CSS на сегодняшний день. Очень удобно добавить display: flex в оболочку и расположить дочерние элементы рядом друг с другом.

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

Вот типичный пример. У нас есть группа параметров, которые должны отображаться рядом друг с другом.

.options-list { display: flex; }
.options-list { display: flex; }

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

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

.options-list {
        display: flex;
        flex-wrap: wrap;
        }
отлично, теперь выглядит адекватно!
отлично, теперь выглядит адекватно!

Расстояние

Мы, верстальщики, должны учитывать разную длину контента. Это означает, что отступы должны быть добавлены к элементу даже если кажется, что они не нужны.

В этом примере у нас есть заголовок раздела и кнопка действия справа. Сейчас выглядит нормально. Но давайте посмотрим, что прозойдёт, если заголовок будет длиннее:

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

.section__title {
        margin-right: 1rem; /* Отступ справа */
        }

Как обрезать текст точками, читаем ниже:

Длинный контент

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

Для меня это "безопасный" подход к CSS. Приятно получить возможность исправить «проблему» до того, как она произойдет.

Вот список имен людей, и сейчас он выглядит идеально.

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

См. следующий пример:

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

.username {
        white-space: nowrap; /* Запрещаем перенос строк */
        overflow: hidden; /* Обрезаем все, что не помещается в область */
        text-overflow: ellipsis; /* Добавляем многоточие в конце */
        }
Другое дело!
Другое дело!

Предотвращение искажения изображения

Если изображение не соответствует размерам, которые мы указали в CSS, оно по умолчанию будет сжиматься или растягиваться. Рассмотрим пример - вот карточка еды, в которой изображение соответствует размерам области:

Когда пользователь загружает изображение другого размера, оно будет искажено. Это нехорошо! Посмотрите, как растянуто изображение:

фото искажается, т.к. пытается заполнить всю область карточки целиком
фото искажается, т.к. пытается заполнить всю область карточки целиком

Чтобы изображение не искажалось. мы используем object-fit:

.card__image { object-fit: cover; }

На уровне проекта я предпочитаю добавлять object-fitко всем изображениям , чтобы избежать неожиданных результатов изображения.

img { object-fit: cover; }

Обратите внимание, что изображение заполняет собой всю область img, а те части изображения которые не умещаются, мы не увидим.

Блокировка цепочки прокрутки

Вы когда-нибудь открывали модальное окно и начинали скроллить, а затем, когда вы доходите до конца и продолжаете прокручивать, содержимое под модальным окном (элемент body) будет прокручиваться? Это называется цепочкой прокрутки (scroll chaining).

В последние годы было несколько хаков, чтобы избежать этого, но теперь мы можем сделать это только с помощью CSS, благодаря свойству ‌overscroll-behavior

На следующем рисунке вы видите поведение цепочки прокрутки по умолчанию.

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

.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;
        }

Чтобы избежать утечки контента из блока, нам нужно использовать min-heightвместо height:

.block {
        min-height: 350px;
        }
Таким образом, если содержимое станет больше, макет не сломается - контейнер станет больше по высоте
Таким образом, если содержимое станет больше, макет не сломается - контейнер станет больше по высоте

Фиксированная ширина

Вы когда-нибудь видели кнопку, контент которой расположен слишком близко к левому и правому краям? Это связано с использованием фиксированной ширины.

.button {
        width: 100px;
        }

Если текст кнопки длиннее 100px, он будет близко к краям. Если текст слишком длинный, он будет просачиваться из кнопки. Это нехорошо!

Чтобы исправить это, мы можем просто заменить widthна min-width.

.button {
        min-width: 100px;
        }

Отключение повторения фона

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

В основном это не будет видно на экране ноутбука, но его можно четко увидеть на больших экранах.

Чтобы заранее избежать такого поведения, обязательно нужно написать, что фон повторяться не должен!

.image {
        background-image: url('..');
        background-repeat: no-repeat; /* запрет повторения изображения */
        }

Вертикальные @media-запросы

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

Вот пример, который я видел несколько раз. У нас есть боковая секция с основным и второстепенным меню. Второстепенное меню должно располагаться в самом низу боковой секции. Разработчик добавил position: sticky к второстепенному меню, чтобы оно могло прилипать к низу. На первый взгляд всё хорошо:

Проблема в том, что если высота браузера будет меньше, все сломается. Обратите внимание, как два меню наползают друг на друга:

Используя вертикальные медиа-запросы CSS, мы можем избежать этой проблемы.

@media (min-height: 600px) {
        .secondary_menu {
        position: sticky;
        bottom: 0;
        }
        }

Таким образом, второстепенное меню будет привязано к нижней части только в том случае, если высота области просмотра больше или равна 600px . Гораздо лучше, правда?

Вероятно, есть лучшие способы реализации такого поведения (например, использование margin-auto), но в этом примере я сосредоточился на вертикальном запросе.

Использование justify-content: space-between

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

Рассмотрим следующий пример:

У нас есть гибкий контейнер с четырьмя элементами. Расстояние между каждым элементом не является следствием использованияgapили margin, оно существует, потому что в контейнере есть justify-content: space-between.

.wrapper {
        display: flex;
        flex-wrap: wrap;
        justify-content: space-between;
        }

Когда количество элементов меньше четырех, вот что произойдет:

Это нехорошо. У данной проблемы есть несколько разных решений

Для простоты я буду использовать gap:

.wrapper {
        display: flex;
        flex-wrap: wrap;
        gap: 1rem;
        }

Текст поверх изображений

При использовании текста поверх изображения важно учитывать случай, когда изображение не загружается. Как будет выглядеть текст?

Вот пример:

Текст выглядит читаемым, но когда изображение не загружается, белый текст теряется на фоне белой страницы:

Мы легко исправим это, добавив фоновый цвет к <img>элементу. Этот фон будет виден только в том случае, если изображение не загрузится. Разве это не круто?

.card__img {
        background-color: grey;
        }

Будьте осторожны с фиксированными значениями в 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.

Рассмотрим следующий пример:

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

При overflow-y: auto полоса прокрутки будет видна только в том случае, если содержимое длинное. Иначе её там не будет:

.element {
        overflow-y: auto;
        }

Ширина контента с полосой прокрутки

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

Рассмотрим следующий пример:

Обратите внимание, как сдвинулось в ширину содержимое, в результате добавления полосы прокрутки. Мы можем зафиксировать ширину контента, чтобы скроллбар её не менял. Для этого напишем стили:

.element {
        scrollbar-gutter: stable;
        }

Минимальный размер контента в CSS-Flexbox

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

Рассмотрим следующий пример:

.card {
        display: flex;
        }

Если в заголовке очень длинное слово, оно не будет переноситься на новую строку.

Даже если мы используем 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 Результат:

Минимальный размер содержимого в CSS-Grid

Подобно flexbox, CSS-сетка имеет минимальный размер содержимого по умолчанию для своих дочерних элементов, который равенauto. Это означает, что если есть элемент, который больше, чем элемент сетки, сетка переполнится.

В приведенном выше примере у нас есть карусель в основном разделе. Для контекста, вот 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-контейнер, который не переносит собственное содержимое на другую строку, её ширина больше, чем секция в которой она находится, в результате этого присутствует горизонтальная прокрутка.

Чтобы исправить это, у нас есть три разных решения:

В качестве "безопасного" CSS я бы выбрал вариант который использует функцию minmax()

@media (min-width: 1020px) {
        .wrapper {
        display: grid;
        grid-template-columns: minmax(0, 1fr) 248px;
        grid-gap: 40px;
        }
        }

Auto Fit против Auto Fill

При использовании функции CSS-Grid minmax()важно выбрать между использованием параметров auto-fitили auto-fill. При неправильном использовании это может привести к неожиданным результатам.

При использовании функции minmax()параметрauto-fit расширяет элементы сетки, чтобы заполнить доступное пространство. В свою очередьauto-fillсохранит доступное пространство зарезервированным, без изменения ширины элементов сетки. Наглядный пример:

При этом, использование auto-fitможет привести к тому, что элементы сетки будут слишком широкими, особенно если они меньше ожидаемого. Рассмотрим следующий пример.

.wrapper {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
        grid-gap: 1rem;
        }

Если используется только один элемент сетки auto-fit, он будет расширяться, чтобы заполнить ширину контейнера:

В большинстве случаев такое поведение не требуется, поэтому лучше использовать auto-fill

.wrapper {
        display: grid;
        grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
        grid-gap: 1rem;
        }

Максимальная ширина IMG

Не забудьте установить max-width: 100%для всех изображений, чтобы они не вылезали за пределы блоков, в которых находятся. Это можно добавить к предыдущим глобальным стилям для img, вот что получится:

img {
        max-width: 100%;
        object-fit: cover;
        }

Использование position: sticky в CSS-Grid

Вы когда-нибудь пробовали использовать position: stickyс дочерним элементом grid-контейнера? По умолчанию, элементы grid растягиваются. В результате боковой элемент в приведенном ниже примере равен высоте основной секции.

Чтобы заставить его работать должным образом, вам нужно сбросить свойство align-self

aside {
        align-self: start;
        position: sticky;
        top: 1rem;
        }

Группировка селекторов для разных браузеров

Не рекомендуется группировать селекторы, предназначенные для работы с разными браузерами. Например, для стилизации заполнителя ввода требуется несколько селекторов для каждого браузера. Если мы сгруппируем селекторы, согласно 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 например), если ещё не использовали их, поверьте, это очень удобно. И самое главное - наслаждайтесь процессом!

 

 

на главную сниппетов
Курсы