Использование DRY в CSS — это способ максимально избегать повторения в таблицах стилей. Этот подход не панацея, но он достаточно эффективен и является одним из основных методов оптимизации. Поскольку я использовал и изучал его почти 10 лет, в этой статье хочу поделиться своим опытом и знаниями.
А если вам будет интересна тема оптимизации CSS, то я рассказал об основах в моей небольшой книге "CSS Optimization Basics".
Основные шаги
Прежде чем мы углубимся в подробности, как «высушить» таблицы с помощью DRY, обратите внимание, что этот метод нетривиально автоматизировать из-за каскадности. Но, не смотря на это, я постараюсь ничего не упустить.
- Пишите CSS обычным и естественным образом.
- Определитесь с DRY-границами, что вы будете оптимизировать: раздел (функционально разделенные CSS-стили), файл, компонент или @media-запросы. Я работаю на уровне файл/@media, то есть обычно «сушу» всё в максимально возможном объеме.
- Убедитесь, что формат кода является консистентным, так как
border:0;
,border: 0;
иborder: none;
означают одно и тоже, но это значительно усложняет поиск дубликатов. - Найдите повторяющиеся объявления:
- Для новых стилей: после первоначальной инициализации.
- Для новых функций и исправлений: после завершения соответствующих действий.
- Совет: если для изменений в файле недостаточно подсказки системы версионирования, просто временно сделайте отступ для измененных объявлений для последующий проверки на уникальность.
- Разрешите повторяющиеся объявления:
- Проверьте каждое объявление (в новых таблицах стилей) или каждое изменённое объявление на предмет повторений в заданном диапазоне (если дедупликация ограничена отдельными разделами, сузьте поиск этими разделами).
- Для каждого повторного объявления (переход к действиям):
- Определите, какое правило в таблице стилей должно быть первым (для этого вы должны определить, какой путь вы выбираете для сортировки селекторов).
- Если первое правило содержит дополнительные объявления, которые еще не были проверены, то скопируйте всё правило и вставьте его после оригинала. Сохраните обнаруженный дубликат в первом правиле и удалите другие объявления, и сделайте наоборот во втором правиле, чтобы оно было похоже на старое правило, только без объявления, которое будет выполняться более одного раза.
- Скопируйте селекторы других правил, которые содержат соответствующее объявление, в правило, идущее первым.
- Обязательно удалите повторяющиеся объявления, селекторы которых были только что скопированы в таблицу стилей, и удалите правило, если оно состоит только из перемещенного дублирующего объявления.
- (Повторите шаги).
- Убедитесь, что правила, обрабатывающие ранее повторяющиеся объявления, содержат селекторы в правильном порядке.
- Убедитесь, что правила, обрабатывающие ранее повторяющиеся объявления, имеют правильное расположение.
Весь этот процесс, на первый взгляд, может показаться запутанным и пугающим, но сейчас мы погрузимся немного глубже и рассмотрим примеры.
Особые случаи
Существует два сценария, требующих особого внимания:
- Разделение файлов: отдельные CSS-файлы могут быть полезны, особенно при повторной сборке в эксплуатацию, но когда дело доходит до «сушки» объявлений, они создают «жесткий» барьер: требуется много усилий для поиска и удаления повторяющихся объявлений. Если мы работаем с небольшой или средней кодовой базой на CSS, то может быть разумно перейти на одну таблицу стилей. Но когда мы имеем дело со сложными таблицами, то некоторое повторение допустимо.
- Строгость подхода или отступление от него: если мы строго избегаем повторений (то есть полностью хотим удалить дубликаты), мы всё равно будем иногда сталкиваться с исключениями. Эти исключения, кроме структурно-зависимых, таких как @media-запросы, файловые границы, или когда последовательность (каскад) является важной, будут вызывать проблемы с поддержкой. Селекторные хаки также являются исключением, поскольку некоторые селекторы работают таким образом, что фактически не позволяют пользовательскому агенту корректно интерпретировать соответствующее правило. В таких случаях мы не можем избежать совпадений, потому что при объединении соответствующих селекторов мы можем повлиять на их работу и результат будет некорректный.
Примеры
Теперь давайте поработаем с несколькими классическими таблицами без использования препроцессора. Мы сосредоточимся на оптимизации на уровне файла, чтобы можно было представить соответствующий код в виде секции или модуля, который выполняет одно и то же действие.
Оптимизация. Пример первый
Этот пример взят с сайта www.engadget.com, где мы обнаружили 92% повторений, случайный раздел, но по-крайней мере отсортированный в алфавитном порядке. Мы предполагаем, что порядок селекторов нас устраивает, и не будем менять и комментировать имена классов и т. д.
.arrow-left { border-color: transparent; border-style: solid; border-width: 10px 10px 10px 0; height: 0; width: 0; } .arrow-down { border-bottom: 10px solid transparent; border-left: 10px solid transparent; border-right: 10px solid transparent; border-top: 10px solid #2b2d32; bottom: 0; height: 0; left: 20px; width: 0; } .faq-list .faq-item-title { cursor: pointer; } .faq-list .faq-item-title:after { border-left: 5px solid transparent; border-right: 5px solid transparent; border-top: 5px solid #111; content: ''; display: inline-block; float: right; height: 0; opacity: .5; vertical-align: top; width: 0; } #contact input:focus, #contact textarea:focus { border: 1px solid #3398db; } .flickity-slider>.table { table-layout: fixed; } ::selection { background: #9b59b6; color: #fff; } .videoWrapper { height: 0; padding-bottom: 56.25%; position: relative; } .videoWrapper iframe { height: 100%; left: 0; position: absolute; top: 0; width: 100%; } .i-rail-followus__socials { display: table; } .i-rail-followus__tw { vertical-align: sub; }
Когда мы смотрим на эту таблицу, то сразу хотим быстро удалить повторяющиеся объявления. Но поскольку мы находимся в самом начале «сушки», не будем отходить от алгоритма и просто спускаемся по таблице сверху вниз, просматривая каждое объявление. Первым является border-color: transparent;
. Оно где-нибудь еще используется? Нет, тогда идём к следующему — border-style: solid;
. Оно уникальное. Тоже самое с border-width: 10px 10px 10px 0;
. Затем height: 0;
. Не уникальное.
Соответственно, наша работа начинается с height: 0;
. Заметим, что это объявление используется в трех различных правилах: .arrow-down
, .faq-list .faq-item-title:after
и .videoWrapper
. Поскольку у нас нет правила, которое включает в себя именно эти четыре селектора, мы копируем их, чтобы сформировать новое правило непосредственно перед тем, в котором мы нашли первое вхождение height: 0;( .arrow-left)
. Другими словам, мы пишем новое правило только для height: 0
;.
.arrow-left, .arrow-down, .faq-list .faq-item-title:after, .videoWrapper { height: 0; }
Теперь убираем height: 0;
из всех остальных правил. Поскольку ни одно из них не состояло исключительно из этого объявления, мы не можем удалить их целиком (пока). Обычно я делаю это за один шаг: если нахожу «бесспорный» дубликат, то создаю новое правило, ищу больше вхождений, удаляю объявление, которое нужно переместить, и копирую селектор(ы), который нужно вставить. С опытом всё это становится проще.
Продолжим с правилом .arrow-left
, с объявлением, следующим после height: 0; — width: 0
. Дубликат? Да. И это хорошо, потому что когда мы проверяем, где еще используется width: 0;
, то видим, что оно почти такое же, но не идентичное селекторам, использующим height: 0;
. То есть мы начнем с нового правила для width: 0;
, убедившись, что удалили все предыдущие вхождения:
.arrow-left, .arrow-down, .faq-list .faq-item-title:after { width: 0; }
Это правило идет после созданного для height: 0;
и перед .arrow-letf;
, которое мы только что проверили и оптимизировали. Я считаю, что результирующий порядок полезен, потому что предпочитаю, чтобы правила располагались в порядке важности и воздействия. А поскольку мы объединяли правила, то сделали их более эффективными.
Давайте проработаем правило .arrow-down;
. В нём нет повторений, хотя его можно описать элегантнее: border: 10px solid transparent; border-top-color: #2b2d32;
.
Продолжим с .faq-list .faq-item-title
. Тут нет дубликатов. На самом деле этот фрагмент таблицы стилей был довольно простым, поэтому мы больше не находим дополнительных совпадений.
.arrow-left, .arrow-down, .faq-list .faq-item-title:after, .videoWrapper { height: 0; } .arrow-left, .arrow-down, .faq-list .faq-item-title:after { width: 0; } .arrow-left { border-color: transparent; border-style: solid; border-width: 10px 10px 10px 0; } .arrow-down { border-bottom: 10px solid transparent; border-left: 10px solid transparent; border-right: 10px solid transparent; border-top: 10px solid #2b2d32; bottom: 0; left: 20px; } .faq-list .faq-item-title { cursor: pointer; } .faq-list .faq-item-title:after { border-left: 5px solid transparent; border-right: 5px solid transparent; border-top: 5px solid #111; content: ''; display: inline-block; float: right; opacity: .5; vertical-align: top; } #contact input:focus, #contact textarea:focus { border: 1px solid #3398db; } .flickity-slider>.table { table-layout: fixed; } ::selection { background: #9b59b6; color: #fff; } .videoWrapper { padding-bottom: 56.25%; position: relative; } .videoWrapper iframe { height: 100%; left: 0; position: absolute; top: 0; width: 100%; } .i-rail-followus__socials { display: table; } .i-rail-followus__tw { vertical-align: sub; }
Оптимизация. Пример второй
Для второго примера возьмём eBay. На этот раз мы тоже хотим просто переформатировать (отступы, сортировка объявлений по алфавиту), но снова сталкиваемся с практиками, которых следует избегать (особенно c именами классов или отсутствием резервных шрифтов, но для шрифтов вроде Arial это не является проблемой).
.sh-GifCont { color: #999; font-family: Verdana !important; font-size: 10px; font-weight: normal; padding: 0 10px 4px 10px; } .sh-GetFastImg { background-image: url('http: //ir.ebaystatic.com/pictures/aw/pics/de/viewitem/spr4VI.png'); background-position: 0 -178px; background-repeat: no-repeat; float: left; height: 16px; margin-right: 4px; width: 56px; } .sh-float-l { float: left; } .sh-FrZip { padding: 10px 0 0 15px; width: 12%; } .sh-FrDelLoc { padding: 10px 15px 0 10px; width: 10%; } .sh-FrCnt { padding-left: 10px; padding-right: 0; text-align: left; } .sh-FrZipCnt { padding: 0 0 0 15px; } .sh-FrDelLocCnt { padding: 0; } .sh-FrBtn { padding: 5px 15px 10px 8px; } .sh-FrDelSite { padding: 6px 0 0; } .vi-frs-sh-FrSlctr { display: inline; padding: 6px 15px 0 10px; } .sh-FrZipDiv { display: inline; padding: 6px 15px 0 0; } .sh-FrTxt { color: #333; font-family: Arial; font-size: 12px; font-weight: normal; padding-left: 15px; } .sh-FrLrnMore { display: inline; padding: 10px 10px 10px 15px; } .sh-FrQuote { display: inline; } .sh-FrLnk { margin-top: 5px; } .sh-Tbl { padding: 10px; } .sh-TblCnt { color: #333; font-family: Arial; font-size: 12px; font-weight: normal; padding-left: 15px; } .sh-TblHdr { color: #5d5d5d; font-family: Verdana; font-size: 12px; font-weight: normal; padding-left: 15px; } .sh-Info { color: #999; font-family: Arial; font-size: 11px; font-weight: normal; } .sh-FrSbTxt { color: #999; font-family: arial; font-size: 11px; font-weight: normal; padding-left: 15px; } .sh-FreightHdr { color: #333; font-family: Verdana !important; font-size: 10px; font-weight: normal; padding: 5px 0 5px 23px; } .sh-Freight-Hdr { color: #333; font-family: Verdana !important; font-size: 10px; font-weight: normal; padding-left: 13px; } .sh-Cnt { color: #5d5d5d; font-family: Arial; font-size: small; font-weight: normal; padding-left: 13px; } .vi-frs-sh-TxtCnt { color: #333; font-family: Arial; font-size: 12px; font-weight: normal; } .sh-BtnTxt { color: #333; font-family: Verdana,Tahoma,Arial; font-size: 12px; font-weight: normal; height: 24px; margin: 0; padding: 0 3px; position: relative; text-decoration: none; top: 0; } .sh-bubble-position { float: left; padding-top: 5px; } .sh-del-lrge b { font-size: 15px; } .sh-gspFirstLine { color: #333; font-family: Arial; font-size: 15px; padding: 25px 10px 5px 0; } .sh-gspSecondLine { color: #777; font-family: Arial; font-size: 12px; padding: 0 10px 15px 0; }
Первый шаг: color: #999;
был использован более одного раза? Да. Итак, мы сначала создаем собственное правило:
.sh-GifCont { color: #999; }
Следует добавить селекторы для двух других вхождений:
.sh-GifCont, .sh-Info, .sh-FrSbTxt { color: #999; }
Здесь нам также помогает предположение, что таблица стилей уже использует тот порядок выбора, который нам нужен, и что смена правил не вызовет проблем. Полезно иметь четкое представление о порядке выбора и о решении каскадных проблем, не перемещая затронутые правила или не объединяя их.
font-family: Verdana !important;
— это объявление используется трижды. Затем font-size: 10px;
— обратите внимание, важно избегать повторения селекторов: это объявление используется теми же селекторами, что мы сгруппировали для «Verdana»
.
.sh-GifCont, .sh-FreightHdr, .sh-Freight-Hdr { font-family: Verdana !important; } .sh-GifCont, .sh-FreightHdr, .sh-Freight-Hdr { font-size: 10px; }
Резюмируем правила:
.sh-GifCont, .sh-FreightHdr, .sh-Freight-Hdr { font-family: Verdana !important; font-size: 10px;}
Таким образом мы проработаем всю таблицу стилей, пока не получим:
.sh-GifCont, .sh-Info, .sh-FrSbTxt { color: #999; } .sh-GifCont, .sh-FreightHdr, .sh-Freight-Hdr { font-family: verdana !important; font-size: 10px; } .sh-GifCont, .sh-FrTxt, .sh-TblCnt, .sh-TblHdr, .sh-Info, .sh-FrSbTxt, .sh-FreightHdr, .sh-Freight-Hdr, .sh-Cnt, .vi-frs-sh-TxtCnt, .sh-BtnTxt { font-weight: normal; } .sh-GifCont { padding: 0 10px 4px 10px; } .sh-GetFastImg, .sh-float-l, .sh-bubble-position { float: left; } .sh-GetFastImg { background-image: url('http: //ir.ebaystatic.com/pictures/aw/pics/de/viewitem/spr4VI.png'); background-position: 0 -178px; background-repeat: no-repeat; height: 16px; margin-right: 4px; width: 56px; } .sh-FrZip { padding: 10px 0 0 15px; width: 12%; } .sh-FrDelLoc { padding: 10px 15px 0 10px; width: 10%; } .sh-FrCnt { padding-left: 10px; padding-right: 0; text-align: left; } .sh-FrZipCnt { padding: 0 0 0 15px; } .sh-FrDelLocCnt { padding: 0; } .sh-FrBtn { padding: 5px 15px 10px 8px; } .sh-FrDelSite { padding: 6px 0 0; } .vi-frs-sh-FrSlctr, .sh-FrZipDiv, .sh-FrLrnMore, .sh-FrQuote { display: inline; } .vi-frs-sh-FrSlctr { padding: 6px 15px 0 10px; } .sh-FrZipDiv { padding: 6px 15px 0 0; } .sh-FrTxt, .sh-TblCnt, .sh-FreightHdr, .sh-Freight-Hdr, .vi-frs-sh-TxtCnt, .sh-BtnTxt, .sh-gspFirstLine { color: #333; } .sh-FrTxt, .sh-TblCnt, .sh-Info, .sh-FrSbTxt, .sh-Cnt, .vi-frs-sh-TxtCnt, .sh-gspFirstLine, .sh-gspSecondLine { font-family: arial; } .sh-FrTxt, .sh-TblCnt, .sh-TblHdr, .vi-frs-sh-TxtCnt, .sh-BtnTxt, .sh-gspSecondLine { font-size: 12px; } .sh-FrTxt, .sh-TblCnt, .sh-TblHdr, .sh-FrSbTxt { padding-left: 15px; } .sh-FrLrnMore { padding: 10px 10px 10px 15px; } .sh-FrLnk { margin-top: 5px; } .sh-Tbl { padding: 10px; } .sh-TblHdr, .sh-Cnt { color: #5d5d5d; } .sh-TblHdr { font-family: verdana; } .sh-Info, .sh-FrSbTxt { font-size: 11px; } .sh-FreightHdr { padding: 5px 0 5px 23px; } .sh-Freight-Hdr, .sh-Cnt { padding-left: 13px; } .sh-Cnt { font-size: small; } .sh-BtnTxt { font-family: verdana,tahoma,arial; height: 24px; margin: 0; padding: 0 3px; position: relative; text-decoration: none; top: 0; } .sh-bubble-position { padding-top: 5px; } .sh-del-lrge b, .sh-gspFirstLine { font-size: 15px; } .sh-gspFirstLine { padding: 25px 10px 5px 0; } .sh-gspSecondLine { color: #777; padding: 0 10px 15px 0; }
Редактирование
То, что мы рассмотрели выше, может показаться большой сложной работой. Но всё, что для этого нужно, это воля, практика и понимание, что сложная и утомительная работа возникает только тогда, когда вы выполняете ее с полностью несортированными, неоптимизированными таблицами стилей. Как только мы проясним наши стандарты написания кода и шаги по оптимизации, обновлять и поддерживать таблицы стилей становится довольно просто. Достаточно будет перепроверить свои правки.
Давайте также рассмотрим это очень кратко в третьем примере, отрывке из Code Responsible.
h1, h2 { color: #000; font-family: futurastd-book, futura, 'droid sans', 'helvetica neue', helvetica, sans-serif; font-weight: 400; line-height: 1.13; } h1 { font-size: 1.86em; margin: 0 0 .53em; } h2 { counter-increment: counter; font-size: 1.5em; margin: 1em 0 0; }
Здесь мы можем легко изменить правило: предположим, что для h1
нужен другой margin
, к примеру, margin: 1em 0 0;
. Многие сразу поймут, что мы можем и должны сделать. А чтобы показать один из возможных способов, с более сложной таблицей стилей я поступил бы так.
Первое, что нужно сделать, это внести изменение, отметить его (мой редактор — обычно IntelliJ IDEA — показывает изменения и упрощает их поиск, но мне всё равно нравится временно отступать от новых или измененных объявлений) и протестировать:
h1, h2 { color: #000; font-family: futurastd-book, futura, 'droid sans', 'helvetica neue', helvetica, sans-serif; font-weight: 400; line-height: 1.13; } h1 { font-size: 1.86em; margin: 1em 0 0; } h2 { counter-increment: counter; font-size: 1.5em; margin: 1em 0 0; }
Во-вторых, после успешного тестирования я бы проверил все эти измененные строки, не нужно ли их оптимизировать еще раз. Здесь мы обнаружим, что поля для h1
и h2
идентичны.
Следуя способу разбивки на отдельные шаги, создадим правило для h1
и h2
. Но у них уже есть собственное правило, поэтому объединим их в одно. Результат:
h1, h2 { color: #000; font-family: futurastd-book, futura, 'droid sans', 'helvetica neue', helvetica, sans-serif; font-weight: 400; line-height: 1.13; margin: 1em 0 0; } h1 { font-size: 1.86em; } h2 { counter-increment: counter; font-size: 1.5em; }
Этим я хотел показать, что поддерживать таблицы гораздо легче, чем оптимизировать их.
Подсказки
Хочу поделиться несколькими советами (поскольку я обновляю статьи на meiert.com, я могу со временем изменить эти советы).
- Поиск без учета регистра. Вполне возможно, что различие регистра является не случайным (некоторые области таблицы стилей, такие как сгенерированный контент или URL-адреса, могут быть чувствительны к регистру). eBay является хорошим примером: независимо от отсутствующих резервных шрифтов, некоторые из используемых объявлений шрифтов пишутся с заглавной буквы по-разному. Например, мы находим
font-family: Arial;
иfont-family: arial;
. Их следует объединить, а значит версия с корректировкой на повторение использует только одно объявление и только в одной нотации (нижний регистр). - Закрывайте каждое (предварительное) объявление точкой с запятой. Это упрощает просто копирование и перемещение объявлений, а также избавляет нас от множества ложных срабатываний (а их будет несколько). Тег
!important
является прекрасным примером: если мы ищем «незакрытые» выражения, тоborder: 0
обязательно совпадёт сborder: 0 !important
, и может появиться бесконечное количество других действительно разных объявлений. Это усложнит нашу работу и повысит вероятность ошибок. - Используйте стандартный порядок выбора. Мы не только ограничиваем энтропию таблицы стилей, но и, поскольку новые правила «автоматически» попадают в четко определенные места, получаем естественную линию защиты от повторения селектора. И мы не хотим, чтобы в статьях, подобных этой, селекторы оставались «сухими». Подумайте, можете ли вы использовать мой дизайн упорядочивания селекторов, тем самым помогая сообществу веб-разработчиков стандартизировать его, или создать свой собственный.
Требования к инструментам
Хотя мы продолжаем изучать эффективность этого подхода и способы его улучшения, есть также несколько задач, решение которых наши инструменты могут облегчить.
- Редакторы могут помочь нам избежать повторяющихся объявлений, выделяя их. Лично я думаю, что небольшой символ «!» в конце строки, настраиваемый в каждом редакторе, был бы замечательным решением, как и возможность игнорировать или отключать уведомления для определенных строк. Это значительно упростило бы работу по оптимизации: не только для отслеживания избыточности, но и для получения представления о том, насколько проблематичен тот или иной случай. некоторых случаях таблицы стилей на 90% состоят из повторений.
- Я уверен, у вас остались вопросы; но надеюсь, что их осталось меньше, чем раньше, когда мы в течение многих лет пренебрегали этой оптимизацией. С моей точки зрения, DRY CSS позволил бы нам более трезво взглянуть на переменные и другие функции, которые попали в спецификации CSS.
Эти вопросы, конечно, следует принимать во внимание, и я надеюсь, что вы поможете нам всем поработать над ними. Пример с Яндексом показал, что отсутствие повторяющихся объявлений — не панацея. Это помогает сделать CSS компактнее и управляемее, но всё же есть крайние случаи, когда он усложняется. Нам будет полезно изучить эти моменты.
Наконец, я считаю, что избегание повторяющихся объявлений является важным способом работы с CSS. Фактически, я не вижу никакого другого метода оптимизации, помимо создания ваших личных рекомендаций по коду, потому что без систематического подхода к на предотвращению повторения вся наша работа просто увеличивает энтропию, которая не помогает писать качественный код. Но именно к нему мы стремимся как профессионалы. Давайте уделим больше внимания качеству кода и парадигме DRY CSS, и посмотрим, что мы можем автоматизировать, не забывая о своем мастерстве.
ссылка на оригинал статьи https://habr.com/ru/company/domclick/blog/530620/
Добавить комментарий