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

в 20:26, , рубрики: Без рубрики

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

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

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

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

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

Расстояние

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

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

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

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

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

Безопасный CSS, или как писать универсальные стили - 6
.section__title {
    margin-right: 1rem; /* Отступ справа */
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

.card__image { object-fit: cover; }

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

img { object-fit: cover; }

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

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

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

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

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

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

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

.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;
}
Безопасный CSS, или как писать универсальные стили - 13

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

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

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

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

.button {
    width: 100px;
}

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

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

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

.button {
    min-width: 100px;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • Margin

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

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

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

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

.wrapper {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
}
Безопасный CSS, или как писать универсальные стили - 21

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

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

Вот пример:

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

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

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

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

.card__img {
    background-color: grey;
}
Безопасный CSS, или как писать универсальные стили - 24

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

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

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

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

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

.element {
    overflow-y: auto;
}
Безопасный CSS, или как писать универсальные стили - 26

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

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

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

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

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

.element {
    scrollbar-gutter: stable;
}
Безопасный CSS, или как писать универсальные стили - 28

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

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

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

.card {
    display: flex;
}

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

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

Даже если мы используем 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, или как писать универсальные стили - 30

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

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

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

В приведенном выше примере у нас есть карусель в основном разделе. Для контекста, вот 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;
    }
}
Безопасный CSS, или как писать универсальные стили - 32

Auto Fit против Auto Fill

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

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

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

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

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

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

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

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

.wrapper {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
    grid-gap: 1rem;
}
Безопасный CSS, или как писать универсальные стили - 35

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

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

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

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

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

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

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

aside {
    align-self: start;
    position: sticky;
    top: 1rem;
}
Безопасный CSS, или как писать универсальные стили - 37

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

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

Автор: Андрей Рик

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js