Статьи

Sass Theming: Бесконечная история

Построение грид-систем и тематических движков — это бесконечная история Sass. В настоящее время, вероятно, существует столько же систем сетки на основе Sass, что и фреймворков JavaScript, и почти у каждого есть свой способ работы с цветными темами в своих таблицах стилей.

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

Допустим, у нас есть компонент, такой как классический медиа-объект . Теперь предположим, что у нас разные цветовые схемы для этого компонента, например, палитра для категории. Для категории A наш объект будет иметь красный заголовок и красную рамку, а для категории B наш объект будет иметь более голубую цветовую палитру.

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

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

$themes: ( 'unicorn': ( 'primary': hotpink, 'secondary': pink ), 'dragon': ( 'primary': firebrick, 'secondary': red ) ) !default; 

Каждая из наших тем состоит из двух цветов: первичного и вторичного. Мы могли бы назвать их по-разному, как альфа и бета , это не имеет значения. Дело только в том, чтобы иметь возможность получить «основной цвет из темы», например.

Как на самом деле работает тематика?

Конечно, есть разные способы, но обычно тема — это не что иное, как класс, к которому привязаны определенные стили. Этот класс может быть добавлен либо к элементу body чтобы обернуть целые страницы, либо к определенным компонентам, чтобы объединить только небольшой модуль, как мы уже рассматривали ранее.

В целом, в вашей таблице стилей вы, вероятно, ожидаете любой из этих двух вариантов:

 .theme-class .component { /* Style for the component when child of `.theme-class` */ } .component.theme-class { /* Style for the component when has `.theme-class` */ } 

Индивидуальный подход mixins

Давайте начнем с простого и, вероятно, моего фаворита: индивидуальный подход к миксинам. Проще говоря, у вас есть пара миксинов, названных в честь свойства, которое они намерены стилизовать. Например, theme-color для их определения цвета или theme-background-color для определения фона.

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

 /** * Media object * 1. Make the border-color use the secondary color of the theme */ .media { margin: 15px; padding: 15px 0; border-top: 5px solid; float: left; @include border-color('secondary'); /* 1 */ } /** * Media title * 1. Make the heading color use the primary color of the theme */ .media__title { font-size: 1em; margin: 0 0 10px; @include color('primary'); /* 1 */ } 

Согласитесь, это довольно элегантно. Более того, я чувствую, что это очевидно , даже без комментариев.

Код

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

  

А теперь или публичный API:

 /// Shorthand to themify color through `themify` mixin /// @access public /// @see {mixin} themify @mixin color($arguments...) { @include themify('color', $arguments...); } /// Shorthand to themify border-color through `themify` mixin /// @access public /// @see {mixin} themify @mixin border-color($arguments...) { @include themify('border-color', $arguments...); } /// Shorthand to themify background-color through `themify` mixin /// @access public /// @see {mixin} themify @mixin background-color($arguments...) { @include themify('background-color', $arguments...); } 

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

 .media { margin: 15px; padding: 15px 0; border-top: 5px solid; float: left; } .media.theme-unicorn, .theme-unicorn .media { border-color: pink; } .media.theme-dragon, .theme-dragon .media { border-color: red; } .media__title { font-size: 1em; margin: 0 0 10px; } .media__title.theme-unicorn, .theme-unicorn .media__title { color: hotpink; } .media__title.theme-dragon, .theme-dragon .media__title { color: firebrick; } 

Pros

  • Благодаря миксинам с именами свойств API-интерфейс является чистым и понятным даже для неопытного разработчика.

Cons

  • Этот подход предполагает использование нескольких миксинов вместо одного, который может или не может рассматриваться как дополнительная сложность, даже если большинство из них являются просто клонами.
  • Поскольку цвета выбираются непосредственно из карты цветов на основе их ключа, невозможно манипулировать ими с помощью цветовых функций, таких как lighten или mix . Это может быть выполнимо с расширенной версией миксина, хотя.

Блочный миксин подход

Подход блочного миксина использует один миксин вместо нескольких и в значительной степени зависит от использования директивы @content . Использование этого будет выглядеть примерно так:

 /** * Media object * 1. Make the border-color use the secondary color of the theme */ .media { margin: 15px; padding: 15px 0; border-top: 5px solid; float: left; @include themify { border-color: $color-secondary; /* 1 */ } } /** * Media title * 1. Make the heading color use the primary color of the theme */ .media__title { font-size: 1em; margin: 0 0 10px; @include themify { color: $color-primary; /* 1 */ } } 

Код

Идея проста: выставить два цвета в виде переменных внутри mixin. Проблема в том, что мы не можем сделать это чистым способом. Переменные, определенные в mixin, не доступны для контента, передаваемого через @content согласно документации:

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

Из-за этого ограничения мы должны обойти это. @content самом деле не так уж и сложен: перед @content мы определяем одну глобальную переменную для каждого цвета в теме, а после вывода @content мы @content эти переменные. Таким образом, они доступны только при вызове themify .

  

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

 .media { margin: 15px; padding: 15px 0; border-top: 5px solid; float: left; } .media.theme-unicorn, .theme-unicorn .media { border-color: pink; } .media.theme-dragon, .theme-dragon .media { border-color: red; } .media__title { font-size: 1em; margin: 0 0 10px; } .media__title.theme-unicorn, .theme-unicorn .media__title { color: hotpink; } .media__title.theme-dragon, .theme-dragon .media__title { color: firebrick; } 

Pros

  • В отличие от индивидуального решения mixins, блок mixin дает возможность манипулировать цветами с помощью функций, поскольку у нас есть прямой доступ к цветам, хранящимся в переменных темы.
  • Я чувствую, что API все еще быстро проясняет этот подход, особенно с учетом того, что мы используем реальные объявления CSS внутри вызова mixin, что может быть проще для понимания некоторыми людьми.

Cons

  • Я должен сказать, что использование глобальных переменных таким способом является своего рода хакерством. Ничего страшного, но качество кода на самом деле не является плюсом.

Большой старый тематический миксин подход

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

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

 // Somewhere in the project, the `themify` mixin @mixin themify($theme, $colors) { // See `Code` section } @include themify; /** * Media object */ .media { margin: 15px; padding: 15px 0; border-top: 5px solid; float: left; } /** * Media title */ .media__title { font-size: 1em; margin: 0 0 10px; } @each $theme, $colors in $themes { @include themify($theme, $colors); } 

Код

  

Pros

  • Поскольку содержание миксина поддерживается вручную, оно обеспечивает высокую гибкость с помощью селекторов. Вы можете делать там все, что захотите.
  • По той же причине он также дает возможность манипулировать цветами с такими функциями, как darken .

Cons

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

Классовый подход

Классовый подход на самом деле основан на DOM. Идея состоит в том, что вместо применения определенных стилей темы из таблицы стилей вы вместо этого добавляете классы темы в свою разметку, такую ​​как border-color-primary . Эти классы, созданные с помощью Sass, ничего не делают сами по себе, но применяют некоторые стили при использовании в сочетании с нашими вечными theme-$theme .

Вы можете прочитать больше об этой системе в этой статье от Гарри Робертса.

 <div class="media theme-unicorn border-color-primary"> <img class="media__image" src="http://lorempixel.com/100/100" /> <h2 class="media__title color-secondary">This is the headline</h2> <p class="media__content">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Provident nulla voluptatibus quisquam tenetur quas quidem, repudiandae vel beatae iure odit odio quae.</p> </div> 
 // Somewhere in the stylesheet (once) @mixin themify($themes: $themes) { // See `Code` section } @include themify($themes); /** * Media object */ .media { margin: 15px; padding: 15px 0; border-top: 5px solid; float: left; } /** * Media title */ .media__title { font-size: 1em; margin: 0 0 10px; } 

Код

 /// Themify mixin /// @param {Map} $themes [$themes] - Map of themes to use @mixin themify($themes: $themes) { // Properties to output, more can be added (eg `border-left-color`) $properties: ('border-color', 'background-color', 'color'); // Iterate over the themes @each $theme, $colors in $themes { // Iterate over the colors from the theme @each $color-name, $color in $colors { // Iterate over the properties @each $property in $properties { // Create a selector // eg `.theme .color-primary, .theme.color-primary` .theme-#{$theme} .#{$property}-#{$color-name}, .theme-#{$theme}.#{$property}-#{$color-name} { #{$property}: $color; } } } } } 

Вывод этого кода будет:

 .theme-unicorn .border-color-primary, .theme-unicorn.border-color-primary { border-color: hotpink; } .theme-unicorn .background-color-primary, .theme-unicorn.background-color-primary { background-color: hotpink; } .theme-unicorn .color-primary, .theme-unicorn.color-primary { color: hotpink; } .theme-unicorn .border-color-secondary, .theme-unicorn.border-color-secondary { border-color: pink; } .theme-unicorn .background-color-secondary, .theme-unicorn.background-color-secondary { background-color: pink; } .theme-unicorn .color-secondary, .theme-unicorn.color-secondary { color: pink; } .theme-dragon .border-color-primary, .theme-dragon.border-color-primary { border-color: firebrick; } .theme-dragon .background-color-primary, .theme-dragon.background-color-primary { background-color: firebrick; } .theme-dragon .color-primary, .theme-dragon.color-primary { color: firebrick; } .theme-dragon .border-color-secondary, .theme-dragon.border-color-secondary { border-color: red; } .theme-dragon .background-color-secondary, .theme-dragon.background-color-secondary { background-color: red; } .theme-dragon .color-secondary, .theme-dragon.color-secondary { color: red; } 

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

Pros

  • Преимущество этого решения состоит в том, что он основан на DOM-подходе, который оказывается очень интересным, когда приходится манипулировать темами на лету с помощью JavaScript. На самом деле, это только вопрос добавления / удаления классов темы в / из элементов; очень удобно.
  • Несмотря на то, что выходные данные в themify могут выглядеть большими, на самом деле это довольно СУХОЙ подход, поскольку каждый тематический цвет, бордюр, цвет фона и whatelse применяются через эти классы.

Cons

  • В некоторых случаях подход на основе DOM может быть неправильным способом, например, когда разметка не создается вручную (CMS, пользовательский контент…).

Последние мысли

Я уверен, что забыл, возможно, дюжину других способов применения стилей темы с помощью Sass, но я чувствую, что эти 4 разные версии уже охватывают хорошую область темы. Если я получу свой выбор, мы либо пойдем на индивидуальный подход к миксинам, либо на путь DOM с классами, если мы хотим стать очень модульными (что всегда хорошо).

Как насчет тебя, как ты это делаешь?