Защитный CSS — коллекция сниппетов
Защитный CSS — это коллекция сниппетов помогающая вам в написании CSS, который будет защищён. Другими словами, в будущем у вас будет меньше проблем. Если вы следите за моим блогом, вы могли прочитать статью «The just in case mindset». Эта статья основывается на ней и будет текущим списком сниппетов.
Перенос Flexbox
CSS Flexbox — одна из самых полезных функций CSS макета. Заманчиво, добавить display: flex
к элементу и дочерние элементы выстроятся один за другим.
Дело в том, что когда места недостаточно, по умолчанию, дочерние элементы не переносятся на новую строку. Нам нужно изменить это поведение с помощью flex-wrap: wrap
.
Вот типичный пример. У нас есть группа опций, которые должны отображаться рядом друг с другом.
.options-list {
display: flex;
}
Когда места становится меньше, появляется горизонтальная прокрутка. Этого следовало ожидать, и на самом деле это не "проблема".
Обратите внимание, как предметы по-прежнему находятся радом друг за другом. Что бы исправить это, нам нужно разрешить перенос.
.options-list {
display: flex;
flex-wrap: wrap;
}
Общее практическое правило при использовании flexbox
— разрешить перенос, если вам не нужна горизонтальная прокрутка. Это другое дело, но попробуйте использовать flex-wrap
, чтобы избежать неожиданного поведения макета (в нашем случае горизонтальной прокрутки).
Отступ
Нам, разработчикам, необходимо учитывать разную длину контента. Это означает, что к компоненту нужно добавить отступ, даже если он кажется ненужным.
В этом примере, у нас есть заголовок раздела и кнопка с правой стороны. На данный момент всё в порядке. Но давайте посмотрим, что произойдёт, если заголовок станет длиннее.
Заметили, что текст расположен слишком близко к кнопке? Возможно, вы думаете о многострочном переносе, но я вернусь к этому в другом разделе. А пока давайте сосредоточимся на отступе.
Если в заголовке есть отступы и усечение текста, мы не увидим такой проблемы.
.section__title {
margin-right: 1rem;
}
Длинный контент
Учёт длинного контента важен при создании макета. Как вы могли видеть в предыдущем случае, заголовок раздела обрезается, если он слишком длинный. Это необязательно, но для некоторых пользовательских интерфейсов важно учитывать.
Для меня это защитный CSS подход. Приятно решить «проблему» до того, как она действительно случится.
Вот список имён людей, и на данный момент он выглядит идеально.
Однако, поскольку это контент, созданный пользователями, мы должны быть осторожны с тем, как защитить макет на случай, если кажется что-то окажется слишком длинным.
Смотрите, следующий рисунок:
В таких макетах важна согласованность. Для этого мы можем просто обрезать имя, используя text-overflow
и его друзей.
.username {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
Если вы хотите отточить свои навыки обработки длинного контента в CSS, я написал подробную статью на эту тему.
Предотвращение растягивания или сжатия изображения
Когда у нас нет контроля над соотношением сторон изображения на веб странице, лучше подумать заранее и предложить решение когда пользователь загружает изображение, которое не соответствует нужному соотношению сторон.
В следующем примере у нас компонент карточки с фотографией. Выглядит хорошо.
Когда пользователь загружает изображение другого размера, оно будет растянуто. Это нехорошо. Посмотрите, как растягивается изображение!
Самый простой способ исправить это — использовать CSS свойство object-fit
.
.card__thumb {
object-fit: cover;
}
На уровне проекта, я предпочитаю добавлять object-fit
ко всем изображениям, чтобы избежать неожиданных результатов.
img {
object-fit: cover;
}
Узнать больше об object-fit
вы можете из статьи «A Deep Dive Into object-fit And background-size In CSS» на Smash Magazine
Блокировка цепочки прокрутки
Вы когда-нибудь открывали модальное окно и начинали прокрутку, а затем когда прокручивали до конца и продолжали прокрутку, контент под модальным окном (элемент body) тоже прокручивался? Это называется цепочка прокрутки.
Было придумано несколько хаков, чтобы предотвратить это. Но, теперь мы можем делать это с помощью CSS свойства overscroll-behavior
.
На следующем рисунке вы увидите поведение цепочки прокрутки по умолчанию.
Чтобы избежать этого, мы можем добавить к любому компоненту, который необходимо прокручивать (например, компонент чата, мобильное меню и т.д.). Самое приятное в этом свойстве, что оно не будет действовать, пока элемент прокручивается.
.modal__content {
overscroll-behavior-y: contain;
overflow-y: auto;
}
Если вы хотите узнать об этом свойстве по больше, я написал более подробную статью.
Резервные значения 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). В противном случае в этом нет необходимости.
Использование фиксированной ширины или высоты
Одна из распространённых вещей, нарушающих макет, — это использование фиксированной ширины или высоты с элементом, который имеет содержимое разной длинны.
Фиксированная высота
Я часто вижу секцию hero
с фиксированной высотой и содержимым, превышающим эту высоту, что приводит к нарушению макета. Не знаете, как это выглядит? Вот так:
.hero {
height: 350px;
}
Что бы контент не выходил за пределы секции hero
, нужно использовать min-height
вместо height
.
.hero {
min-height: 350px;
}
Таким образом, если контент станет больше, макет не сломается.
Фиксированная ширина
Вы когда-нибудь видели кнопку, название которой расположено слишком близко к левому и правому краям? Это связано с использованием фиксированной ширины.
.button {
width: 100px;
}
Если подпись кнопки длиннее 100 пикселей, она будет слишком близко к краям. Если она будет слишком длинной, текст будет выползать за края. Это нехорошо!
Чтобы исправить это, мы можем просто заменить width
на min-width
.
.button {
min-width: 100px;
}
Забытый background-repeat
Часто используя большое фоновое изображение, мы забываем учитывать тот случай, когда страница просматривается на большом экране. По умолчанию, фон будет повторяться.
Это не видно на экране ноутбука, но хорошо видно на экранах большего размера.
Чтобы заранее избежать такого поведения, обязательно сбросьте значение background-repeat
.
.hero {
background-image: url('..');
background-repeat: no-repeat;
}
Вертикальные медиа-запросы
Иногда так заманчиво создать компонент и протестировать его изменяя только ширину окна браузера. Тестирование с учётом высоты браузера может выявить некоторые интересные проблемы.
Вот одна из них, с которой я сталкивался несколько раз. У нас есть дополнительный компонент с основными и второстепенными ссылками.
Рассмотрим следующий пример. Основная и дополнительная навигация выглядят нормально. В примере, который я видел, разработчик добавил position: sticky
для вторичной навигации, что бы она могла прилипать к низу.
Однако, если высота браузера меньше, всё ломается. Обратите внимание, как два навигационных блока перекрываются.
Используя вертикальный медиа-запрос CSS, мы можем избежать этой проблемы.
@media (min-height: 600px) {
.aside__secondary {
position: sticky;
bottom: 0;
}
}
Таким образом, вторичная навигация будет прикреплена к низу только в том случае, если высота области просмотра больше или равна 600px
. Намного лучше, правда?
Вероятно есть более эффективные способы реализовать это поведение (например, с помощью margin-top: auto
), но в этом примере я сосредоточусь на вертикальном медиа-запросе.
Если я захочу объяснить использование вертикального медиа-запроса CSS, мне нужно написать об этом целую статью. Хорошая новость в том, что я уже написал её, если вам интересно.
Использование justify-content: space-between
В контейнере flex
вы можете использовать justify-content
, чтобы отделить дочерние элементы друг от друга. С определённым количеством дочерних элементов макет будет выглядеть нормально. Однако, когда их становиться больше или меньше макет выглядит странно.
Рассмотрим следующий пример.
У нас flex
контейнер с четырьмя дочерними элементами. Расстояние между каждым дочерним элементом — это не 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
Допустим у нас есть Grid
, который содержит aside
и main
. CSS выглядит так:
.wrapper {
display: grid;
grid-template-columns: 250px 1fr;
gap: 1rem;
}
Это сломается при небольших размерах области просмотра из-за нехватки места. Чтобы избежать такой проблемы, всегда используйте медиа-запросы при использовании CSS Grid, как показано ниже.
@media (min-width: 600px) {
.wrapper {
display: grid;
grid-template-columns: 250px 1fr;
gap: 1rem;
}
}
Показывайте полосу прокрутки только когда это необходимо
К счастью, мы можем управлять отображением полосы прокрутки показывать её или нет в случае длинного контента. При этом настоятельно рекомендуется использовать auto
в качестве значения overflow
.
Рассмотрим следующий пример.
Обратите внимание, что даже если контента мало, полоса прокрутки видна. Это плохо для пользовательского интерфейса. Как дизайнера меня смущает видимая полоса прокрутки, когда в ней нет необходимости.
.element {
overflow-y: auto;
}
С overflow-y: auto
полоса прокрутки будет видна только в том случае, если контента много. Иначе её там не будет. Вот обновлённый рисунок.
Место для полосы прокрутки
Ещё одна вещь, связанная с полосой прокрутки, — это место для полосы прокрутки (дословно жёлоб полосы прокрутки). Если взять предыдущий пример, когда контент становится длиннее, появление полосы прокрутки вызовет сдвиг макета. Резервирование места для полосы прокрутки поможет избежать сдвига макета.
Рассмотрим следующий рисунок.
Обратите внимание, как содержимое смещалось, когда оно становилось длиннее в результате отображения полосы прокрутки. Мы можем избежать такого поведения, используя свойство scrollbar-gutter
.
.element {
scrollbar-gutter: stable;
}
Минимальный размер содержимого CSS Flexbox
Если flex
элемент содержит текст или изображение, размер которого больше, чем длинна элемента, браузер не сжимает их. Это поведение по умолчанию для Flexbox
.
Рассмотрим следующий пример.
.card {
display: flex;
}
Когда в заголовке содержится очень длинное слово, оно не переносится на новую строку.
Даже если мы используем overflow-break: break-word
, это не сработает.
.card__title {
overflow-wrap: break-word;
}
Для изменения такого поведения по умолчанию, на нужно установить min-width
элемента равную 0
. Это связано с тем, что значение по умолчанию для min-width
— auto
, происходит переполнение.
.card__title {
overflow-wrap: break-word;
min-width: 0;
}
То же самое применимо и для столбца flex
, но используется min-height: 0
.
Минимальный размер содержимого в CSS Grid
Подобно Flexbox, CSS Grid имеет минимальный размер для содержимого своих дочерних элементов равный auto
. Это значит, что если есть элемент больше элемента Grid, будет переполнение.
В приведённом выше примере, у нас есть карусель в разделе main
. Вот 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 контейнер, который не переносится. Его ширина больше, чем у секции main
, элемент grid это учитывает. В результате появляется полоса прокрутки.
Чтобы исправить это, у нас есть три разных решения:
- Использовать
minmax()
- Применить
min-width
к элементу grid - Добавить
overflow: hidden
к элементу grid
В качестве защитного CSS механизма я бы выбрал первый, который использует функцию minmax()
.
@media (min-width: 1020px) {
.wrapper {
display: grid;
grid-template-columns: minmax(0, 1fr) 248px;
grid-gap: 40px;
}
}
Я писал об этом в начале года. Я также настоятельно рекомендую посмотреть «Preventing a Grid Blowout» и «You want minmax(10px, 1fr) not 1fr»Криса Койера (Chris Coyier).
auto-fit
vs auto-fill
Когда используется GSS Grid функция minmax()
важно выбрать правильно между используемыми ключевыми словами auto-fit
или auto-fill
. При неправильном использовании это может привести к неожиданным результатам.
При использовании функции minmax()
, ключевое слово auto-fit
будет расширять элементы Grid, что бы заполнить всё доступное пространство. В то время как auto-fill
сохраняет доступное пространство зарезервированным без изменения ширины элементов Grid.
При этом использование auto-fit
может привести к тому, что элементы Grid будут слишком широкими, особенно когда они меньше ожидаемого. Рассмотрим следующий пример.
.wrapper {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
grid-gap: 1rem;
}
Если есть только один элемент Grid и используется auto-fit
, элемент будет расширяться, что бы заполнить ширину контейнера.
В большинстве случаев в таком поведении нет необходимости поэтому на мой взгляд, лучше использовать auto-fill
.
.wrapper {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
grid-gap: 1rem;
}
Максимальная ширина изображения
Не забывайте устанавливать max-width: 100%
для всех изображений. Это можно добавить к CSS сбросу, который вы используете.
img {
max-width: 100%;
object-fit: cover;
}
position: sticky
в CSS Grid
Вы когда-нибудь пробовали использовать position: sticky
с дочерним элементом Grid контейнера? По умолчанию элементы Grid растягиваются. В результате элемент aside
, в нижнем примере равен по высоте секции main
.
Что бы он работал правильно, вы нужно сбросить свойство align-self
.
aside {
align-self: start;
position: sticky;
top: 1rem;
}
Я подробно писал на эту тему в своём блоге, если вам интересно. Или в статье «Прилипающие CSS Grid элементы»
Группировка селекторов
Не рекомендуется группировать селекторы, предназначенные для работ с разными браузерами. Например, для стилизации атрибута placeholder
требуется несколько селекторов для каждого браузера. Если мы сгруппируем селекторы, всё правило будет не действительным, согласно w3c
/* Не делайте так, пожалуйста */
input::-webkit-input-placeholder,
input:-moz-placeholder {
color: #222;
}
Вместо этого, делайте так.
input::-webkit-input-placeholder {
color: #222;
}
input:-moz-placeholder {
color: #222;
}
Это не конец
Это ещё не конец, но мне очень понравилось документировать все эти техники. Это постоянный список защитных приёмов CSS, которые я лично использую в зависимости от проекта, над которым работаю. Если вам есть что предложить, свяжитесь со мной через twitter @shadeed9.