В этой статье мы проведем рефакторинг CSS-кода одного проекта с проблемами специфичности.
Определение
Правила
При определении того, какие именно свойства CSS нужно применить к элементу, браузер использует исходный порядок следования набора стилей CSS (то есть каскадность). Но это правило применимо лишь в случаях, когда CSS-селекторы обладают равной специфичностью. Что же произойдет, если специфичность одного селектора окажется выше специфичности другого?
В таком случае будет использоваться специфичность CSS-селектора. Чем выше его специфичность, тем больше шансов, что браузер применит именно это объявление CSS.
nav a {
color: green;
}
a {
color: red;
}
В примере выше оба CSS селектора нацелены на одинаковый HTML элемент – якорный тег. Для определения нужного правила браузер рассчитывает значения специфичности и проверяет, какое из них является наивысшим. В данном случае, высшее значение оказалось у первого селектора, поэтому к якорному тегу браузер применит именно его.
Важный момент:
!important
– это не CSS- селектор, а ключевое слово, которое принудительно переопределяет CSS-правила вне зависимости от значения специфичности, источника или исходного порядка следования селекторов. Несколько примеров использования:
Несмотря на кажущуюся важность !important
проблем от него намного больше, чем пользы. Со временем осложняется поддержка CSS и ухудшается читаемость таблицы стилей. Особенно от этого страдают те, кто работает/будет работать с кодом в будущем.
Пара слов о проекте, который будем рефакторить: это лендинг в стиле Netflix с использованием интерфейса MovieDB.
Таблица стилей
Основная цель – удалить ключевое слово “!important” из CSS-правил и провести рефакторинг кода, чтобы он применял только правила специфичности.
Ниже представлена таблица стилей проекта.
@import url("https://fonts.googleapis.com/css?family=Montserrat:400,400i,700");
body {
margin: 0;
padding: 0;
overflow-x: hidden;
}
.wrapper {
width: 100%;
}
.wrapper #header {
position: fixed;
z-index: 300;
padding: 15px;
width: calc(100% - 30px);
display: flex;
justify-content: space-between;
align-items: center;
background: linear-gradient(to bottom, black 0%, transparent 100%);
}
.wrapper #header #brand-logo {
color: #d32f2f;
text-shadow: 1px 1px 2px black;
letter-spacing: 5px;
text-transform: uppercase;
font-family: Montserrat;
font-weight: bold;
font-size: 22px;
}
.wrapper #header #menu-icon {
display: none;
}
.wrapper #header .nav-link,
.wrapper #header .icon {
color: #bdbdbd;
cursor: pointer;
}
.wrapper #header .nav-menu {
width: 400px;
display: flex;
justify-content: space-around;
align-items: center;
}
.wrapper #header .nav-link {
padding: 5px 10px;
font-size: 15px;
font-family: century gothic;
text-decoration: none;
transition: background-color 0.2s ease-in;
}
.wrapper #header .nav-link:hover {
color: #c62828;
background-color: rgba(0, 0, 0, 0.7);
}
.wrapper #header .icon {
font-size: 16px;
}
.wrapper #header .icon:hover {
color: #c62828;
}
.wrapper #site-banner,
.wrapper #categories {
width: 100%;
}
.wrapper #site-banner {
height: 550px;
background-image: url("https://s1.gifyu.com/images/rampage_2018-1024x576.jpg");
background-size: cover;
background-position: center;
background-repeat: no-repeat;
background-attachment: fixed;
}
.wrapper #site-banner .main-movie-title,
.wrapper #site-banner .watch-btn,
.wrapper #site-banner .main-overview {
position: absolute;
z-index: 3;
}
.wrapper #site-banner .main-movie-title, .wrapper #site-banner .watch-btn {
text-transform: uppercase;
}
.wrapper #site-banner .main-movie-title {
top: 120px;
left: 20px;
background: -webkit-linear-gradient(#ff9100, #dd2c00);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-size: 55px;
font-family: Montserrat;
font-weight: bold;
}
.wrapper #site-banner .main-overview {
width: 400px;
top: 230px;
left: 25px;
color: #fafafa;
line-height: 25px;
font-family: helvetica;
}
.wrapper #site-banner .watch-btn {
width: 150px;
height: 35px;
top: 350px;
left: 25px;
border: none;
border-radius: 20px;
color: #fafafa;
cursor: pointer;
transition: all 0.2s ease-in;
background-color: #ff0000;
box-shadow: 1px 5px 15px #940000;
}
.wrapper #site-banner .watch-btn:hover {
color: #F5F5F5;
background-color: #940000;
}
.wrapper .after {
position: relative;
top: 0;
left: 0;
z-index: 2;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.3);
}
.wrapper #categories {
padding: 30px 0;
display: flex;
flex-direction: column;
background: linear-gradient(to top, #090909 0%, #000000 100%);
overflow: hidden;
}
.wrapper #categories .category {
margin: 30px 0;
}
.wrapper #categories .category-header, .wrapper #categories .content {
margin-left: 20px;
color: #B0BEC5;
font-family: helvetica;
}
.wrapper #categories .category-header {
margin-bottom: 50px;
font-weight: normal;
letter-spacing: 5px;
}
.wrapper #categories .content {
position: relative;
right: 0;
display: flex;
justify-content: flex-start;
transition: all 3s ease-in-out;
}
.wrapper #categories .movie {
margin-right: 10px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
}
.wrapper #categories .movie-img {
transition: all 0.2s ease-in;
}
.wrapper #categories .movie-img:hover {
-webkit-filter: contrast(1.1);
filter: contrast(1.1);
-webkit-transform: scale(1.05);
transform: scale(1.05);
cursor: pointer;
}
.wrapper #footer {
width: 100%;
height: 120px;
background-color: #090909;
display: flex;
align-items: flex-end;
justify-content: flex-start;
}
.wrapper #footer #copyright-label {
margin-left: 20px;
padding: 10px;
color: rgba(255, 255, 255, 0.3);
opacity: 0.7;
letter-spacing: 2px;
font-family: helvetica;
font-size: 12px;
}
//Media Query
@media (max-width: 750px) {
.nav-menu {
visibility: hidden;
}
#menu-icon {
display: block !important;
font-size: 22px;
}
.main-movie-title {
font-size: 45px !important;
}
.main-overview {
width: 350px !important;
font-size: 14px !important;
}
.watch-btn {
width: 130px !important;
height: 25px !important;
font-size: 13px;
}
.movie-img {
width: 170px;
}
}
Мы видим, что чаще всего !important используется в разделе с медиа-запросами, описывающими стили, которые браузер применяет при ширине экрана меньше 750 пикселей.
Что же произойдет, если удалить ключевое слово !important из правил CSS, к которым оно применяется? У нас потеряется «верховод», который принудительно переопределяет CSS правила других селекторов для одного HTML-элемента. А конфликты между CSS-правилами браузер начнет проверять строго по таблице стилей.
При конфликтах приоритетности браузер руководствуется исходным порядком следования, специфичностью и важностью CSS-селектора. Если селекторы с конфликтными правилами обладают одинаковой специфичностью, то браузер учитывает исходный порядок следования и применяет CSS-правила селектора из таблицы сверху вниз. К сожалению, данная процедура не используется нашей таблице стилей.
Если CSS-селекторы с конфликтными правилами обладают разной специфичностью, то браузер выбирает правило селектора с наивысшей специфичностью. Этот принцип подходит нашей таблице стилей. CSS-селекторы в медиа-запросах обладают более низкой специфичностью, чем CSS-селекторы в основной части таблицы стилей.
Проблема определена. Пора переходить к ее решению!
Для начала найдем CSS-селекторы, соответствующие селекторам медиа-запросов.
.wrapper #header #menu-icon {
display: none;
}
.wrapper #site-banner .main-movie-title {
...
font-size: 55px;
...
}
.wrapper #site-banner .main-overview {
width: 400px;
...
}
.wrapper #site-banner .watch-btn {
width: 150px;
height: 35px;
...
}
@media (max-width: 750px) {
#menu-icon {
display: block !important;
...
}
.main-movie-title {
font-size: 45px !important;
}
.main-overview {
width: 350px !important;
font-size: 14px !important;
}
.watch-btn {
width: 130px !important;
height: 25px !important;
...
}
}
Специфичность CSS-селекторов из основной части таблицы стилей выше. Поскольку правила специфичности имеют больший приоритет, чем правила исходного порядка следования, браузер применяет именно их.
Чтобы исправить ситуацию, необходимо увеличить значение специфичности CSS-селекторов в медиа-запросах. Тогда при одинаковой специфичности нескольких селекторов для одного HTML-элемента, браузер будет руководствоваться исходным порядком следования. Тогда при ширине экрана менее 750 пикселей будут применяться CSS-правила из медиа-запросов (которые расположены ниже по таблице стилей).
Конечный результат выглядит так:
.wrapper #header #menu-icon {
display: none;
}
.wrapper #site-banner .main-movie-title {
...
font-size: 55px;
...
}
.wrapper #site-banner .main-overview {
width: 400px;
...
}
.wrapper #site-banner .watch-btn {
width: 150px;
height: 35px;
...
}
@media (max-width: 750px) {
.wrapper #header #menu-icon {
display: block;
...
}
.wrapper #site-banner .main-movie-title {
font-size: 45px;
}
.wrapper #site-banner .main-overview {
width: 350px;
font-size: 14px;
}
.wrapper #site-banner .watch-btn {
width: 130px;
height: 25px;
font-size: 13px;
}
}
Вот и все! Мы удалили все следы присутствия ключевого слова !important
в стилях. Читаемость кода возрастает на глазах. После рефакторинга такую таблицу будет легче применять и поддерживать.
Мы узнали о том, как именно браузеры выбирают CSS стилей по исходному порядку следования, специфичности и источникам селекторов. Познакомились с проблемами, возникающими при использовании !important
в CSS, и научились сводить количество этих ключевых слов в коде к минимуму.
Больше не нужно добавлять !important
для решения проблем – есть куда более достойные решения.
Сама концепция специфичности достаточно объемна. Но стоит разобраться в ней на практическом примере с подробной документацией, как вы научитесь применять эти знания в своих проектах.