Как стать автором
Обновить

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

Время на прочтение11 мин
Количество просмотров34K
Автор оригинала: Ahmad Shadeed

Предисловие: при написании 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;
}

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

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

  • Margin

  • Flexbox gap (используйте с осторожностью)

  • Padding (может применяться к родительскому элементу каждого дочернего элемента)

  • Добавление пустых элементов в качестве разделителя

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

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

  • Использованиеminmax()

  • Применение min-widthк элементу сетки

  • Добавление overflow: hiddenэлемента в сетку

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

Теги:
Хабы:
Всего голосов 25: ↑23 и ↓2+25
Комментарии12

Публикации

Истории

Ближайшие события