Главная » Статьи » Вложенность CSS, специфика и вы

Вложенность CSS, специфика и вы

Вложенность CSS, специфика и вы

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

Как выглядит вложенность CSS?

Если вы работали в Sass или Less, возможно, вы знакомы с вложенностью, которая выглядит следующим образом:

div { background: #fff; p { color: red; }
}

Тот же самый блок, написанный с помощью вложенного CSS, выглядит так:

div { background: #fff; & p { color: red; }
}

Вы заметили & перед p? Это «селектор вложенности», который сообщает браузеру, что вы хотите вложить p в div. Вы можете добавить селектор вложенности только в начале. У нас также есть такая возвожность в Sass, но это нужно только тогда, когда вы хотите добавить что-то в предыдущий селектор, например, класс:

div { background: #fff; &.red { color: red; }
}

И это на самом деле точно так же, как и при вложении собственного CSS. Думайте об этом так, как будто & просто необходим для каждого селектора. Помните, что в CSS, & всегда на первом месте.

Теперь есть способ разместить & где-нибудь еще, и это благодаря правилу вложенности @nest. Добавив к селектору префикс @nest, вы можете выбрать, где вы хотите разместить селектор.

div { background: #fff; @nest section & { color: red; }
}

В приведенном выше коде вы не придаете секции красный цвет, но вы создаете новый селектор section div (& заменяется селектором над ним), который имеет стиль css.

Вы можете добавлять селекторы вложенности только после вашего обычного CSS. Любые свойства CSS после селектора вложенности будут проигнорированы. Таким образом, следующее неверно:

div { background: #fff; & p { color: red; } border: 1px solid;
}

Наконец, вы также можете вкладывать медиа-запросы, убедившись, что вы используете селектор вложенности для перезапуска блока «набора правил».

div { flex-direction: column; @media (min-width: 40rem) { & { flex-direction: row; } }
}

Как и раньше, & будет заменен родительским селектором, поэтому приведенный выше код будет тем же CSS, что и этот:

div { flex-direction: column;
}
@media (min-width: 40rem) { div { flex-direction: row; }
}

Используем вложенность сегодня с postcss-preset-env

Вложенность CSS еще не поддерживается во всех браузерах, но если вы используете PostCSS, вы можете установить плагин PostCSS Preset Env, и PostCSS преобразует вложенный CSS в CSS, который сегодня понятен браузерам.

Чтобы использовать PostCSS, вам понадобится этап сборки, например, с помощью Webpack, Parcel или Gulp. Вы можете найти обзор настроек в документации PostCSS по использованию, так что выберите тот, который вам подходит.

Чтобы поиграть с предустановленным Env PostCSS, посетите их площадку, которая компилирует код на лету.

Простой способ начать работу с postcss-preset-env в вашем проекте — использовать интерфейс командной строки, установив PostCSS CLI и postcss-preset-env:

npm install postcss postcss-cli postcss-preset-env --save-dev

И создадим файл postcss.config.js, который выглядит так:

const postcssPresetEnv = require('postcss-preset-env'); module.exports = { plugins: [ postcssPresetEnv() ]
}

Затем запустите postcss в терминале из той же папки, что и ваш файл конфигурации.

postcss path/to/input.css -o path/to/output.css

Вот как вы можете использовать это сегодня, но когда вы это делаете, есть кое-что, о чем нужно знать, и это специфичность.

Специфичность: большая ловушка

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

Селектор is()

Если вы не знакомы с ним (он относительно новый) — селектор is() позволяет вам обернуть кучу разных селекторов, чтобы предотвратить дублирование:

main h1,
main h2,
main h3 { font-family: fantasy;
} main :is(h1, h2, h3) { font-family: fantasy;
}

Два примера дают один и тот же результат, но обратите внимание, что во втором примере мы написали «main» только один раз, тогда как в первом примере нам пришлось написать его три раза. Когда дело доходит до специфичности, селектор :is() получает свою специфичность от самого определенного элемента в списке.

Незнакомы со специфичностью?

Если элементу задано несколько селекторов, браузер использует специфичнось селектора, чтобы определить, какой стиль применить. Каждый селектор имеет специфичность, которая определяется тем, что вы используете в этом селекторе: элементы, идентификаторы, классы и т. д.

Обычно это выглядит как три корзины, вот так: 1.2.3. Первая — это количество идентификаторов, вторая — количество классов, псевдоклассов и селекторов атрибутов, а последняя содержит элементы и псевдоэлементы.

Одно значение на более высоком уровне важнее, чем все уровни ниже него. Например, у вас может быть один селектор с 1000 именами классов и один селектор с одним идентификатором, последний все равно будет более определенный.

Если вы хотите лучше почувствовать специфичность, я построил калькулятор специфичности CSS, который поддерживает селекторы вплоть до уровня 5. Введите свои селекторы и посмотрите, какой специфичностью они обладают.

Вернемся к селектору :is()

В нашем примере :is(h1, h2, h3) специфичность невысока 0.0.1, так как там только элементы. Но для селектора :is(#start, h1) специфичность :is() резко возрастает 1.0.0 из-за идентификатора.

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

К сожалению, это означает, что вы можете получить очень специфичные селекторы CSS и не знать об этом!
Например, давайте посмотрим на этот «простой» пример списка новостей. Поскольку они сами по себе составляющие, имеет смысл держать их вместе.

main { /* css properties */ & #intro { /* css properties */ & .newsitems { /* css properties */ & div.newsitem { /* css properties */ & h2 { /* css properties */ } & p { /* css properties */ &.meta { /* css properties */ & span.date { /* css properties */ } } & + p { /* css properties */ } } & a { /* css properties */ } } } }
}

Поскольку каждый селектор сам по себе выглядит так просто, вы можете продолжать вложение, прежде чем вы узнаете том, что вы смотрите на правило css, которое выглядит так:

main #intro .newsitems div.newsitem p.meta + p { }

Которое имеет специфику 1.3.4 и перезапишет большинство других CSS-селекторов, которые вы пишете. Делая так достаточно часто, вы обнаружите, что создаете более сложный селектор или добавляете !important где-то, чтобы заставить CSS работать правильно, и к тому времени вы проиграли.

Предотвратить проблемы со специфичностью

Очень соблазнительно просто продолжать вложение (это так просто! и вам нужно так мало печатать!), но чтобы предотвратить проблемы со специфичностью, вам нужно ограничить глубину вложенности и вырваться из нее, если это возможно.

Ограничьте вложенность

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

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

Вырваться из вложенности

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

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

main

#intro

.newsitems

div.newsitem

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

Чтобы сделать это немного понятнее, допустим, в блоке newsitems есть три элемента новостей, и мы хотим, чтобы они были в горизонтальном гибком макете, чтобы элементы располагались рядом.

<div class="newsitems"> <div class="newsitem"> <h2>Title</h2> <p class="meta">posted at <span class="date">date</span> <p>Description</p> <a href="">Link</a> </div> <div class="newsitem"> <h2>Title</h2> <p class="meta">posted at <span class="date">date</span> <p>Description</p> <a href="">Link</a> </div> <div class="newsitem"> <h2>Title</h2> <p class="meta">posted at <span class="date">date</span> <p>Description</p> <a href="">Link</a> </div>
</div>

Для .newsitems мы настроили гибкий макет:

.newsitems { display:flex; justify-content: flex-start; align-items: flex-start; gap: 1rem;
}

Чтобы сделать элементы новостей одинаковой ширины, мы можем добавить к ним flex: 1 1 33%, но это имеет смысл только в контексте .newsitems. Мы могли бы использовать .newsitem в другом месте, так как «flex» не является частью стиля для отдельного новостного сообщения. Мы токже можем сделать селектор немного менее специфичным, изменив & .newsitem на & > div.

.newsitems { display:flex; justify-content: flex-start; align-items: flex-start; gap: 1rem; & > div { flex: 1 1 33%; }
} .newsitem { /* css properties */ & h2 { /* css properties */ }
}

К сожалению, теперь у нас есть два места, где происходит стилизация, и они могут быть в разных местах кода. В идеале мы должны стилизовать каждый элемент только один раз.

Потенциальная проблема заключается в том, что .newsitems > div более специфично, чем .newsitem (0.1.1 против 0.1.0), поэтому любой стиль в .newsitem будет перезаписан во вложенном селекторе CSS. Обычно это то, что вам нужно, но не всегда.

Потенциальный способ обойти это — быть более строгим в отношении стиля, и есть несколько способов, каждый из которых имеет свои преимущества и недостатки.

Разделение между стилями макета и стилями компонентов

Пустые родительские элементы

Автономные компоненты стиля

Разделение между стилями макета и стилями компонентов

Некоторые CSS используются для определения макета, например, position и flex, в то время как другие используются для определения стиля, например, background и font-size. Вы можете сохранить логику макета во вложенном селекторе, а логику стиля — в селекторе верхнего уровня:

.newsitems { display:flex; justify-content: flex-start; align-items: flex-start; gap: 1rem; & > div { flex: 1 1 33%; }
} .newsitem { background: #fff; color: #332; /* more CSS properties */
}

Теперь стиль компонента отделен от его макета в родительском компоненте, что делает его более гибким. Это требует определенной дисциплины, и вам придется искать в разных местах в зависимости от того, хотите ли вы изменить макет или стиль.

Держите пустыми родительские элементы

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

.newsitems { /* no styling here */ & > div { flex: 1 1 33%; background: #fff; }
} .newsitem { /* no styling here */ & h2 { /* css properties */ }
}

Таким образом, вы всегда стилизуете содержимое компонента (как бы) и используете возможности вложения исключительно как средство организации вашего CSS.

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

Автономные компоненты стиля

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

.newsitem { background: #fff; &.in-newsitems { flex: 1 1 33%; } &.in-sidebar { flex: 1 0 100%; } & h2 { /* css properties */ }
}

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

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

Скоро вложенность CSS появится в браузерах!

Автор: Kilian Valkhof

Источник: kilianvalkhof.com

Редакция: Команда webformyself.

Читайте нас в Telegram, VK, Яндекс.Дзен