Требования к CSS препроцессору у меня сформировались после прочтения одной из статей здесь. Это была статья про препроцессор «Stylus для Node.js». Собственно, тогда то я про эти «препроцессоры» и узнал. Меня поразила вся простота синтаксиса этого препроцессора. После двухдневного (а может и меньшего) просмотра результатов с гугла, я ничего интересного для себя не нашел. Вот именно в этот момент ко мне в голову и пришла шальная мысль: а почему бы нет?
Требования у меня были следующие:
- Наиболее простой синтаксис (ну это само собой!)
- Язык разработки – PHP (ну теперь то уже можно и сказать)
- Возможность отображения исходного файла стилей в виде дерева блоков
Собственно, наверно, это и все.
Что у меня получилось?
Все это время, прошедшее с начальной точки отсчета, я потратил на разработку своего продукта, и у меня получилось следующее — библиотека MySheet с открытым исходным кодом, являющаяся одновременно и парсером, и препроцессором стилей CSS.
Ну, конечно же, мне пришлось написать небольшой сайтик для презентации своего продукта (вы уже догадались, какой язык программирования был выбран мной для этой цели?). Несколько интересных моментов процесса разработки – о том, как я создавал свою библиотеку, с какими подводными камнями столкнулся и что я узнал в процессе ее создания – я расскажу чуть позже. А теперь, о возможностях моего CSS препроцессора.
- Арифметические выражения
Данная возможность есть практически в каждом CSS препроцессоре. Не обошла она стороной и библиотеку MySheet:
$wrapper_height = 50% .wrapper height $wrapper_height + 20px top ($wrapper_height / 2)
Для чего она нужна? Да Бог его знает. Шутка. Сейчас эта функция моей библиотеки находится в очень сыром виде. Нет типа bool, а следовательно пока что (пока что!) нет условий. Но есть одна приятная плюшка, применение которой вы можете найти на главной странице моего сайта:
.object color #a50c5b - 50sat /* decrease saturation by 50% */ background-color #a50c5b + 50lt /* make color lighter by 50 percent */
Да! Это именно то, о чем вы сейчас думаете! Можно выполнять арифметические действия над цветами! Я надеюсь, вы вдоволь поиграетесь с этой фишкой на официальном сайте библиотеки, а теперь перейдем к следующему пункту в нашем списке.
- Mixin’ы
Ну, вот, не смог я написать это слово на русском. Уж больно многие пишут его на английском:
@mixin filter-grayscale(percent) -webkit-filter: grayscale($percent); -ms-filter: grayscale($percent); -o-filter: grayscale($percent); filter: grayscale($percent); img filter-grayscale 100% img:hover filter-grayscale 0%
Собственно, это уже априори стандартная функция всех препроцессоров. Пользоваться ей очень просто: нужно написать имя миксина вместо имени CSS-правила, а затем передать аргументы в значении данного правила.
В объявлении миксина также доступна переменная $arguments, которая просто перечислит все переданные аргументы в одну строку:
@mixin border-radius(topleft, topright) -webkit-border-radius $topleft $topright 4px 5px border-radius $arguments
Я постарался, чтобы в препроцессор были уже встроены некоторые миксины. Это, во-первых, благоприятно скажется на производительности. Во-вторых, позволит разработчикам и дизайнерам сосредоточиться на написании стилей для сайта, а не методов для упрощения этого процесса. Плюс, второе часто бывает делать лень.
- Функции
Функций в библиотеки пока что всего – три. Это – abs, negate и unitless. Но база для их написания подготовлена, и в будущем список доступных функций будет расширяться.
Синтаксис для вызова функций точно такой же как и синтаксис обычных функций CSS, с той лишь разницей, что при вызове зарегистрированной библиотечной функции в скомпилированном CSS будет фигурировать не она, а значение ею возвращаемое.
- Флаги
Эту возможность я придумал относительно недавно. Смысл ее – простой до безобразия, и я думаю лучше показать сразу пример ее использования:
html height 0 width 50px !prefixWith(ms, moz) !important border-radius 5px !important filter-grayscale 50% transform scale(2)
Флаг в данном случае применяется для удобного добавления префиксов к правилам и компилируется в следующий код CSS:
html { height: 0; -ms-width: 50px !important; -moz-width: 50px !important; width: 50px !important; -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; -webkit-filter: grayscale(50%); filter: grayscale(50%); -ms-transform: scale(2); -moz-transform: scale(2); -o-transform: scale(2); -webkit-transform: scale(2); transform: scale(2) }
Флаг выполняет некоторые действия над конкретным правилом. У меня есть идея использовать флаги еще и для того, чтобы помечать и наделять правила определенными свойствами. Например, так можно сделать флаг !noMixin, который запретит компиляцию и вставку миксина в код CSS. Таким образом можно избежать расширения синтаксиса лишними символами и ключевыми словами.
- Плагины
Я старался сделать свою библиотеку расширяемой. Т.е. чтобы любой человек (и Вы, и я, и, вообще, любая домохозяйка) могли расширить возможности библиотеки написанием плагина, а не созданием форка на гитхаб и переворачиванием всего исходного кода (хотя второе я и не воспрещаю делать). Сейчас написано два плагина для библиотеки MySheet:
— PluginMixin — добавляет возможность использования миксинов в коде MSS (MySheet Styles)
— PluginSelectorExtensions — добавляет вкусняшки вроде: обращение к родительскому селектору (или группе селекторов) через символ & и псевдо-селектор :any(), который я нагло содрал с препроцессора CSSCrush.
- Дерево блоков
Это, собственно, и есть третье по списку требование, о котором я писал в начале статьи. Я хотел, чтобы стилями CSS можно было управлять не только непосредственно, изменяя исходники вручную, но и делать это из бэкэнда, т.е. производить те манипуляции с кодом, которые присущи CSS парсерам (тык и еще тык). Что это дает? Например, можно изменять стили сайта на основании предпочтений пользователя. А предпочтения могут быть самыми разными. Одни хотят шрифт больше, другие – передвинуть заголовок на главной страницы немного ниже. Данную возможность можно добавлять к различным генераторам сайтов, что благоприятно скажется на аудитории пользователей программного продукта.
Переходя от слов к делу, хочется показать, какое именно дерево блоков образуется на выходе после парсинга файла с помощью библиотеки MySheet. Рассмотрим такой простой исходный файл MSS:
html { color red; text-align: center; margin: 0 auto; } @mixin rounded-corners (top, right, bottom, left) -webkit-border-radius \$left + \$right \$top + \$bottom -moz-border-radius \$arguments border-radius \$arguments \$left \$right \$left \$right @mixin diagonal-border-radius(left, right) border-radius \$arguments \$right \$left @page padding 5px body rounded-corners 1 2 3 4 .wrapper diagonal-border-radius 6px 10px h1 span color blue
Пропустим этот код через парсер:
<?php try { $result = $mysheet->parseCode($code); $compiledCode = $result->toRealCss(); } catch (\MSSLib\Error\MySheetException $ex) { echo($ex->getTraceAsString()); }
и на выходе получим примерно следующее дерево:
Дерево блоков MSSobject(MSSLib\Structure\Document)[64] protected '_docFilePath' => null protected 'children' => array (size=5) 0 => object(MSSLib\Structure\Ruleset)[98] private '_selectors' => array (size=1) 0 => object(MSSLib\Structure\Selector)[99] private '_mssPath' => string 'html' (length=4) private '_cssPathGroup' => object(MSSLib\Structure\CssSelectorGroup)[180] private 'paths' => array (size=1) 0 => string 'html' (length=4) private '_ruleset' => &object(MSSLib\Structure\Ruleset)[98] private '_isFullSelector' => null private '_isParsed' => boolean true private '_handlerMap' => null protected '_parentRuleset' => null protected 'children' => array (size=3) 0 => object(MSSLib\Structure\Declaration)[100] private 'ruleName' => string 'color' (length=5) private 'ruleValue' => object(MSSLib\Structure\RuleValue)[101] private 'params' => array (size=1) 0 => object(MSSLib\EmbeddedClasses\ColorClass)[104] protected 'type' => string 'html' (length=4) protected 'color' => array (size=1) 0 => string 'red' (length=3) protected '_colorLib' => null private '_parentDeclaration' => &object(MSSLib\Structure\Declaration)[100] private '_flags' => array (size=0) empty private 'ruleEnabled' => boolean true private 'parent' (MSSLib\Structure\Block) => null private '_handlerMap' => null private '_handlerMap' (MSSLib\Structure\Block) => null 1 => object(MSSLib\Structure\Declaration)[102] private 'ruleName' => string 'text-align' (length=10) private 'ruleValue' => object(MSSLib\Structure\RuleValue)[103] private 'params' => array (size=1) 0 => object(MSSLib\EmbeddedClasses\NonQuotedStringClass)[107] protected 'text' => string 'center' (length=6) private '_parentDeclaration' => &object(MSSLib\Structure\Declaration)[102] private '_flags' => array (size=0) empty private 'ruleEnabled' => boolean true private 'parent' (MSSLib\Structure\Block) => null private '_handlerMap' => null private '_handlerMap' (MSSLib\Structure\Block) => null 2 => object(MSSLib\Structure\Declaration)[105] private 'ruleName' => string 'margin' (length=6) private 'ruleValue' => object(MSSLib\Structure\RuleValue)[106] private 'params' => array (size=2) 0 => object(MSSLib\EmbeddedClasses\MetricClass)[110] protected 'metric' => float 0 protected 'unit' => null 1 => object(MSSLib\EmbeddedClasses\NonQuotedStringClass)[111] protected 'text' => string 'auto' (length=4) private '_parentDeclaration' => &object(MSSLib\Structure\Declaration)[105] private '_flags' => array (size=0) empty private 'ruleEnabled' => boolean true private 'parent' (MSSLib\Structure\Block) => null private '_handlerMap' => null private '_handlerMap' (MSSLib\Structure\Block) => null private 'parent' (MSSLib\Structure\Block) => &object(MSSLib\Structure\Document)[64] private '_handlerMap' (MSSLib\Structure\Block) => null 1 => object(MSSLib\Plugins\Mixin\Mixin)[97] protected 'name' => string 'rounded-corners' (length=15) protected 'locals' => array (size=4) 0 => string 'top' (length=3) 1 => string 'right' (length=5) 2 => string 'bottom' (length=6) 3 => string 'left' (length=4) protected 'children' => array (size=3) 0 => object(MSSLib\Structure\Declaration)[109] private 'ruleName' => string '-webkit-border-radius' (length=21) private 'ruleValue' => object(MSSLib\Structure\RuleValue)[113] private 'params' => array (size=2) 0 => object(MSSLib\EmbeddedClasses\MathExprClass)[122] protected 'expressionTree' => object(MSSLib\Essentials\ExpressionTree\ExpressionNode)[116] private 'value' (Tree\Node\Node) => null private 'parent' (Tree\Node\Node) => null private 'children' (Tree\Node\Node) => array (size=3) 0 => object(MSSLib\Essentials\ExpressionTree\ParamNode)[117] private 'value' (Tree\Node\Node) => object(MSSLib\EmbeddedClasses\VariableClass)[119] private 'varName' => string 'left' (length=4) private 'parent' (Tree\Node\Node) => &object(MSSLib\Essentials\ExpressionTree\ExpressionNode)[116] private 'children' (Tree\Node\Node) => array (size=0) empty 1 => object(MSSLib\Essentials\ExpressionTree\OperatorNode)[118] private 'value' (Tree\Node\Node) => object(MSSLib\Operators\PlusOperator)[120] private 'parent' (Tree\Node\Node) => &object(MSSLib\Essentials\ExpressionTree\ExpressionNode)[116] private 'children' (Tree\Node\Node) => array (size=0) empty 2 => object(MSSLib\Essentials\ExpressionTree\ParamNode)[121] private 'value' (Tree\Node\Node) => object(MSSLib\EmbeddedClasses\VariableClass)[123] private 'varName' => string 'right' (length=5) private 'parent' (Tree\Node\Node) => &object(MSSLib\Essentials\ExpressionTree\ExpressionNode)[116] private 'children' (Tree\Node\Node) => array (size=0) empty 1 => object(MSSLib\EmbeddedClasses\MathExprClass)[130] protected 'expressionTree' => object(MSSLib\Essentials\ExpressionTree\ExpressionNode)[124] private 'value' (Tree\Node\Node) => null private 'parent' (Tree\Node\Node) => null private 'children' (Tree\Node\Node) => array (size=3) 0 => object(MSSLib\Essentials\ExpressionTree\ParamNode)[125] private 'value' (Tree\Node\Node) => object(MSSLib\EmbeddedClasses\VariableClass)[127] private 'varName' => string 'top' (length=3) private 'parent' (Tree\Node\Node) => &object(MSSLib\Essentials\ExpressionTree\ExpressionNode)[124] private 'children' (Tree\Node\Node) => array (size=0) empty 1 => object(MSSLib\Essentials\ExpressionTree\OperatorNode)[126] private 'value' (Tree\Node\Node) => object(MSSLib\Operators\PlusOperator)[128] private 'parent' (Tree\Node\Node) => &object(MSSLib\Essentials\ExpressionTree\ExpressionNode)[124] private 'children' (Tree\Node\Node) => array (size=0) empty 2 => object(MSSLib\Essentials\ExpressionTree\ParamNode)[129] private 'value' (Tree\Node\Node) => object(MSSLib\EmbeddedClasses\VariableClass)[131] private 'varName' => string 'bottom' (length=6) private 'parent' (Tree\Node\Node) => &object(MSSLib\Essentials\ExpressionTree\ExpressionNode)[124] private 'children' (Tree\Node\Node) => array (size=0) empty private '_parentDeclaration' => &object(MSSLib\Structure\Declaration)[109] private '_flags' => array (size=0) empty private 'ruleEnabled' => boolean true private 'parent' (MSSLib\Structure\Block) => null private '_handlerMap' => null private '_handlerMap' (MSSLib\Structure\Block) => null 1 => object(MSSLib\Structure\Declaration)[114] private 'ruleName' => string '-moz-border-radius' (length=18) private 'ruleValue' => object(MSSLib\Structure\RuleValue)[115] private 'params' => array (size=1) 0 => object(MSSLib\EmbeddedClasses\VariableClass)[134] private 'varName' => string 'arguments' (length=9) private '_parentDeclaration' => &object(MSSLib\Structure\Declaration)[114] private '_flags' => array (size=0) empty private 'ruleEnabled' => boolean true private 'parent' (MSSLib\Structure\Block) => null private '_handlerMap' => null private '_handlerMap' (MSSLib\Structure\Block) => null 2 => object(MSSLib\Structure\Declaration)[132] private 'ruleName' => string 'border-radius' (length=13) private 'ruleValue' => object(MSSLib\Structure\RuleValue)[133] private 'params' => array (size=5) 0 => object(MSSLib\EmbeddedClasses\VariableClass)[137] private 'varName' => string 'arguments' (length=9) 1 => object(MSSLib\EmbeddedClasses\VariableClass)[138] private 'varName' => string 'left' (length=4) 2 => object(MSSLib\EmbeddedClasses\VariableClass)[139] private 'varName' => string 'right' (length=5) 3 => object(MSSLib\EmbeddedClasses\VariableClass)[140] private 'varName' => string 'left' (length=4) 4 => object(MSSLib\EmbeddedClasses\VariableClass)[141] private 'varName' => string 'right' (length=5) private '_parentDeclaration' => &object(MSSLib\Structure\Declaration)[132] private '_flags' => array (size=0) empty private 'ruleEnabled' => boolean true private 'parent' (MSSLib\Structure\Block) => null private '_handlerMap' => null private '_handlerMap' (MSSLib\Structure\Block) => null private 'parent' (MSSLib\Structure\Block) => &object(MSSLib\Structure\Document)[64] private '_handlerMap' (MSSLib\Structure\Block) => null protected 'plugin' => object(MSSLib\Plugins\Mixin\PluginMixin)[58] private '_registeredMixins' => array (size=0) empty private '_systemMixins' => array (size=3) 'border-radius' => array (size=2) 0 => object(MSSLib\Plugins\Mixin\EmbeddedMixins\BasicSet)[61] 1 => string 'border_radius' (length=13) 'transform' => array (size=2) 0 => object(MSSLib\Plugins\Mixin\EmbeddedMixins\BasicSet)[61] 1 => string 'transform' (length=9) 'filter-grayscale' => array (size=2) 0 => object(MSSLib\Plugins\Mixin\EmbeddedMixins\BasicSet)[61] 1 => string 'filter_grayscale' (length=16) protected '_enabledMixinSetClasses' => array (size=1) 0 => string 'basic' (length=5) 2 => object(MSSLib\Plugins\Mixin\Mixin)[112] protected 'name' => string 'diagonal-border-radius' (length=22) protected 'locals' => array (size=2) 0 => string 'left' (length=4) 1 => string 'right' (length=5) protected 'children' => array (size=1) 0 => object(MSSLib\Structure\Declaration)[136] private 'ruleName' => string 'border-radius' (length=13) private 'ruleValue' => object(MSSLib\Structure\RuleValue)[142] private 'params' => array (size=3) 0 => object(MSSLib\EmbeddedClasses\VariableClass)[145] private 'varName' => string 'arguments' (length=9) 1 => object(MSSLib\EmbeddedClasses\VariableClass)[146] private 'varName' => string 'right' (length=5) 2 => object(MSSLib\EmbeddedClasses\VariableClass)[147] private 'varName' => string 'left' (length=4) private '_parentDeclaration' => &object(MSSLib\Structure\Declaration)[136] private '_flags' => array (size=0) empty private 'ruleEnabled' => boolean true private 'parent' (MSSLib\Structure\Block) => null private '_handlerMap' => null private '_handlerMap' (MSSLib\Structure\Block) => null private 'parent' (MSSLib\Structure\Block) => &object(MSSLib\Structure\Document)[64] private '_handlerMap' (MSSLib\Structure\Block) => null protected 'plugin' => object(MSSLib\Plugins\Mixin\PluginMixin)[58] private '_registeredMixins' => array (size=0) empty private '_systemMixins' => array (size=3) 'border-radius' => array (size=2) 0 => object(MSSLib\Plugins\Mixin\EmbeddedMixins\BasicSet)[61] 1 => string 'border_radius' (length=13) 'transform' => array (size=2) 0 => object(MSSLib\Plugins\Mixin\EmbeddedMixins\BasicSet)[61] 1 => string 'transform' (length=9) 'filter-grayscale' => array (size=2) 0 => object(MSSLib\Plugins\Mixin\EmbeddedMixins\BasicSet)[61] 1 => string 'filter_grayscale' (length=16) protected '_enabledMixinSetClasses' => array (size=1) 0 => string 'basic' (length=5) 3 => object(MSSLib\Structure\AtRule)[135] protected '_name' => string 'page' (length=4) protected '_parameters' => string '' (length=0) protected 'children' => array (size=1) 0 => object(MSSLib\Structure\Declaration)[143] private 'ruleName' => string 'padding' (length=7) private 'ruleValue' => object(MSSLib\Structure\RuleValue)[148] private 'params' => array (size=1) 0 => object(MSSLib\EmbeddedClasses\MetricClass)[151] protected 'metric' => float 5 protected 'unit' => string 'px' (length=2) private '_parentDeclaration' => &object(MSSLib\Structure\Declaration)[143] private '_flags' => array (size=0) empty private 'ruleEnabled' => boolean true private 'parent' (MSSLib\Structure\Block) => &object(MSSLib\Structure\AtRule)[135] private '_handlerMap' => null private '_handlerMap' (MSSLib\Structure\Block) => null private 'parent' (MSSLib\Structure\Block) => &object(MSSLib\Structure\Document)[64] private '_handlerMap' (MSSLib\Structure\Block) => null 4 => object(MSSLib\Structure\Ruleset)[144] private '_selectors' => array (size=1) 0 => object(MSSLib\Structure\Selector)[150] private '_mssPath' => string 'body' (length=4) private '_cssPathGroup' => object(MSSLib\Structure\CssSelectorGroup)[96] private 'paths' => array (size=1) 0 => string 'body' (length=4) private '_ruleset' => &object(MSSLib\Structure\Ruleset)[144] private '_isFullSelector' => null private '_isParsed' => boolean true private '_handlerMap' => null protected '_parentRuleset' => null protected 'children' => array (size=3) 0 => object(MSSLib\Structure\Declaration)[152] private 'ruleName' => string 'rounded-corners' (length=15) private 'ruleValue' => object(MSSLib\Structure\RuleValue)[153] private 'params' => array (size=4) 0 => object(MSSLib\EmbeddedClasses\MetricClass)[156] protected 'metric' => float 1 protected 'unit' => null 1 => object(MSSLib\EmbeddedClasses\MetricClass)[157] protected 'metric' => float 2 protected 'unit' => null 2 => object(MSSLib\EmbeddedClasses\MetricClass)[158] protected 'metric' => float 3 protected 'unit' => null 3 => object(MSSLib\EmbeddedClasses\MetricClass)[159] protected 'metric' => float 4 protected 'unit' => null private '_parentDeclaration' => &object(MSSLib\Structure\Declaration)[152] private '_flags' => array (size=0) empty private 'ruleEnabled' => boolean true private 'parent' (MSSLib\Structure\Block) => null private '_handlerMap' => null private '_handlerMap' (MSSLib\Structure\Block) => null 1 => object(MSSLib\Structure\Declaration)[154] private 'ruleName' => string 'transform' (length=9) private 'ruleValue' => object(MSSLib\Structure\RuleValue)[155] private 'params' => array (size=1) 0 => object(MSSLib\EmbeddedClasses\FunctionClass)[167] protected 'name' => string 'rotate' (length=6) protected 'arguments' => array (size=1) 0 => object(MSSLib\EmbeddedClasses\MathExprClass)[174] protected 'expressionTree' => object(MSSLib\Essentials\ExpressionTree\ExpressionNode)[170] private 'value' (Tree\Node\Node) => null private 'parent' (Tree\Node\Node) => null private 'children' (Tree\Node\Node) => array (size=2) 0 => object(MSSLib\Essentials\ExpressionTree\OperatorNode)[171] private 'value' (Tree\Node\Node) => object(MSSLib\Operators\UnaryMinusOperator)[172] private 'parent' (Tree\Node\Node) => &object(MSSLib\Essentials\ExpressionTree\ExpressionNode)[170] private 'children' (Tree\Node\Node) => array (size=0) empty 1 => object(MSSLib\Essentials\ExpressionTree\ParamNode)[173] private 'value' (Tree\Node\Node) => object(MSSLib\EmbeddedClasses\MetricClass)[175] protected 'metric' => float 5 protected 'unit' => string 'deg' (length=3) private 'parent' (Tree\Node\Node) => &object(MSSLib\Essentials\ExpressionTree\ExpressionNode)[170] private 'children' (Tree\Node\Node) => array (size=0) empty protected '_functionRenderer' => object(MSSLib\Essentials\FunctionRenderers\DefaultFunctionRenderer)[166] private '_parentDeclaration' => &object(MSSLib\Structure\Declaration)[154] private '_flags' => array (size=0) empty private 'ruleEnabled' => boolean true private 'parent' (MSSLib\Structure\Block) => null private '_handlerMap' => null private '_handlerMap' (MSSLib\Structure\Block) => null 2 => object(MSSLib\Structure\Ruleset)[149] private '_selectors' => array (size=1) 0 => object(MSSLib\Structure\Selector)[161] private '_mssPath' => string '.wrapper' (length=8) private '_cssPathGroup' => object(MSSLib\Structure\CssSelectorGroup)[176] private 'paths' => array (size=1) 0 => string 'body .wrapper' (length=13) private '_ruleset' => &object(MSSLib\Structure\Ruleset)[149] private '_isFullSelector' => null private '_isParsed' => boolean true private '_handlerMap' => null protected '_parentRuleset' => &object(MSSLib\Structure\Ruleset)[144] protected 'children' => array (size=2) 0 => object(MSSLib\Structure\Declaration)[168] private 'ruleName' => string 'diagonal-border-radius' (length=22) private 'ruleValue' => object(MSSLib\Structure\RuleValue)[169] private 'params' => array (size=2) 0 => object(MSSLib\EmbeddedClasses\MetricClass)[178] protected 'metric' => float 6 protected 'unit' => string 'px' (length=2) 1 => object(MSSLib\EmbeddedClasses\MetricClass)[179] protected 'metric' => float 10 protected 'unit' => string 'px' (length=2) private '_parentDeclaration' => &object(MSSLib\Structure\Declaration)[168] private '_flags' => array (size=0) empty private 'ruleEnabled' => boolean true private 'parent' (MSSLib\Structure\Block) => null private '_handlerMap' => null private '_handlerMap' (MSSLib\Structure\Block) => null 1 => object(MSSLib\Structure\Ruleset)[164] private '_selectors' => array (size=1) 0 => object(MSSLib\Structure\Selector)[177] private '_mssPath' => string 'h1 span' (length=7) private '_cssPathGroup' => object(MSSLib\Structure\CssSelectorGroup)[183] private 'paths' => array (size=1) 0 => string 'body .wrapper h1 span' (length=21) private '_ruleset' => &object(MSSLib\Structure\Ruleset)[164] private '_isFullSelector' => null private '_isParsed' => boolean true private '_handlerMap' => null protected '_parentRuleset' => &object(MSSLib\Structure\Ruleset)[149] protected 'children' => array (size=1) 0 => object(MSSLib\Structure\Declaration)[181] private 'ruleName' => string 'color' (length=5) private 'ruleValue' => object(MSSLib\Structure\RuleValue)[182] private 'params' => array (size=1) 0 => object(MSSLib\EmbeddedClasses\ColorClass)[185] protected 'type' => string 'html' (length=4) protected 'color' => array (size=1) 0 => string 'blue' (length=4) protected '_colorLib' => null private '_parentDeclaration' => &object(MSSLib\Structure\Declaration)[181] private '_flags' => array (size=0) empty private 'ruleEnabled' => boolean true private 'parent' (MSSLib\Structure\Block) => null private '_handlerMap' => null private '_handlerMap' (MSSLib\Structure\Block) => null private 'parent' (MSSLib\Structure\Block) => &object(MSSLib\Structure\Ruleset)[149] private '_handlerMap' (MSSLib\Structure\Block) => null private 'parent' (MSSLib\Structure\Block) => &object(MSSLib\Structure\Ruleset)[144] private '_handlerMap' (MSSLib\Structure\Block) => null private 'parent' (MSSLib\Structure\Block) => &object(MSSLib\Structure\Document)[64] private '_handlerMap' (MSSLib\Structure\Block) => null private 'parent' (MSSLib\Structure\Block) => null private '_handlerMap' (MSSLib\Structure\Block) => null
Что в конечном итоге компилируется в следующий CSS-код:
html { color: #ff0000; text-align: center; margin: 0 auto } @page { padding: 5px } body { -webkit-border-radius: 6 4; -moz-border-radius: 1 2 3 4; border-radius: 1 2 3 4 4 2 4 2; -ms-transform: rotate(-5deg); -moz-transform: rotate(-5deg); -o-transform: rotate(-5deg); -webkit-transform: rotate(-5deg); transform: rotate(-5deg) } body .wrapper { border-radius: 6px 10px 10px 6px } body .wrapper h1 span { color: #0000ff }
- Формат вывода CSS
Форматом выходного CSS-кода можно управлять с помощью настроек библиотеки. Для этого следует задать предпочтительные префиксы и суффиксы к строкам:
$mysheet = MySheet::Instance(); $mysheet->setActiveDirectory(realpath('./')); $mysheet->getAutoload()->registerAutoload(); $settings = new MSSettings(); $settings->set('cssRenderer', [ 'prefixRule' => ' ', 'suffixRule' => ' /* this is a real CSS rule */', 'sepSelectors' => ', ', 'sepRules' => '; ', 'prefixOCB' => ' ', 'suffixOCB' => "\n", 'prefixCCB' => "\n", 'suffixCCB' => '' ]); … $mysheet->init($settings);
Приведу таблицу всех возможных префиксов и суффиксов:
Название Значение по-умолчанию Описание prefixRule 4 пробела Строка, вставляемая перед каждым правилом suffixRule Пустая строка Строка, вставляемая после каждого правила sepSelectors , Разделитель между селекторами sepRules ;\n Разделитель между правилами prefixOCB Пробел Строка, вставляемая перед открывающейся фигурной скобкой (OCB – opening curly bracket) suffixOCB \n Строка, вставляемая после открывающейся фигурной скобки prefixСCB \n Строка, вставляемая перед закрывающейся фигурной скобкой (CCB – closing curly bracket) suffixCCB \n Строка, вставляемая после закрывающейся фигурной скобки prefixAtRuleLine 4 пробела Строка, вставляемая перед каждой строкой внутри @-правила suffixAtRuleLine Пустая строка Строка, вставляемая после каждой строки внутри @-правила
- Другие возможности
В библиотеке есть и другие возможности и функции, которые я просто перечислю списком. К ним относятся:
— Включение и отключение компиляции правила (через символ ~, добавляемый перед правилом)
— Автоматическое встраивание мелких изображений в код CSS с помощью data: URL
— Импортирование других MSS и CSS файлов с помощью директивы @ import (над этой возможностью я еще работаю; в частности, нужно добавить возможность задания опций импортирования)
— Преобразование всех цветов к одному формату
— 2 поддерживаемых языка для текста ошибок компиляции: английский (en_us) и русский (ru_ru)
— Включение и отключение расширений парсера библиотеки (можно отключить поддержку функций, переменных, цветов и т.п.)
Как это было…
С описанием того, чего я достиг на сегодняшний день, я вроде бы закончил. А теперь, я хочу рассказать, как я вел процесс разработки – как в перерывах между работой, учебой и моей любимой девчонкой, я писал свою библиотеку.
Рис. 1 – Моя любимая девчонка
С того самого момента, как я затеял свой мини-проектик, прошло уже немало времени. Возникало много разных сомнительных ситуаций и вопросов. И оно не мудрено – до этого я никогда ничего подобного не делал. Никакой специальной литературы по компиляторам я не читал и сначала делал все исключительно на свое усмотрение. Я не хвастаюсь, а даже наоборот говорю, что зря я этого не сделал. Возможно, так бы я избежал некоторых возникавших проблем.
Я почему-то сразу решил делать библиотеку в виде open-source проекта и сразу сказал себе, что одной гитхаб-странички будет недостаточно. После того как часть библиотеки была уже написана, я начал делать для нее сайт, чтобы в «режиме онлайн» находить дефекты и исправлять их, а также сразу на практике осознавать, что было бы приятно видеть конечному пользователю.
Название для своей библиотеки я придумывал, действуя от обратного. Я решил не отходить от примера самых известных на сегодняшний день препроцессоров SASS и LESS, и подумал, что MSS – неплохое сочетание букв, в конце концов. А чтобы название было запоминающимся, я решил назвать свой проект MySheet. И в аббревиатуру укладывается (MySheet Styles), и лёгкая изюминка в названии есть.
Название придумано, пора начинать проектировать корабль. Первое, что я начал делать – это был парсер исходного кода. Вот тут-то я и просчитался в первый раз. Я начал делать его без разбития исходного кода на токены и ключевые слова, и ориентировался на то, что в библиотеке будет фигурировать в-основном работа со строками. Конечно, минусы этого подхода я видел уже на этапе его выбора, но ничего лучшего, к сожалению, я придумать не смог. Уже потом, во время того как в университете у нас читался курс по компиляторам, я понял, что лучше было бы ввести этап предварительного лексического анализа. Хотя бы, потому что сейчас я столкнулся с проблемой распознавания и запоминания комментариев для последующего их вывода в скомпилированный код CSS. Или, например, теперь мне бы хотелось добиться нечувствительности расширений парсера (которые подключаются к библиотеки в виде дополнительных модулей) к наличию нежданных переносов строк и отступов (тех самых, которые часто добавляются для удобочитаемости кода). В ближайшем будущем, я хочу включить в парсер этап разбития на токены, что, по моему мнению, должно разрешить эти проблемы.
Идея с арифметикой цветов мне пришла в голову, когда я реализовывал поддержку математических выражений. Я подумал, что неплохо было бы иметь возможность осветления и затемнения цветов в дизайне сайта, чтобы подобрать сочетающийся цвет можно было без необходимости открытия color picker’а. Сейчас в библиотеке реализована работа с HSLA, RGBA, HEX и HTML форматами цветов. К каждому из цветов, заданных в данных форматах, можно добавить дельту любого канала из какого-либо другого формата цвета. Например, к цвету записанному как #000 можно добавить 255 пунктов синего канала и 40 пунктов зеленого, получив при этом цвет #0028ff. Арифметическое выражение будет выглядеть в данном случае следующим образом: #000 + 255b + 40g.
Реализовывая работу с цветами, я решил не изобретать свой велосипед и использовать уже существующую библиотеку MrColor (хотя без «допиливания» этой библиотеки не обошлось).
Кстати, в этот момент я задумался над оптимизацией библиотеки. В качестве профилировщика кода я использовал Webgrind. Не самый лучший профилировщик, но дело свое он делает, и он мне очень помог в обнаружении слабых мест в моем коде.
Хочу рассказать одну интересную штуку. Когда парсер в препроцессоре начинает парсить какое-либо правило, он проходит по всем зарегистрированным модулям и, грубо говоря, вызывает в каждом из них метод parse. А так как арифметическое выражение и, например, функция – две разные сущности, то библиотеке приходилось парсить одно и то же два раза. Мне это очень не нравилось, и в один прекрасный день я придумал решение. Когда парсер арифметического выражения обнаруживает, что перед ним все ж таки никакое не выражение, а простая функция, он не возвращает false, а возвращает этот самый объект функции. Тем самым я избавился от этого изъяна и увеличил производительность процесса парсинга исходного кода.
Еще хочу рассказать, как я делал свой собственный первый логотип. В поисках идеи для логотипа, я набрел на картинку с тюбиками краски, и подумал: «Тысяча чертей! Да это же просто замечательная идея!». Я посмотрел несколько уроков по рисованию в Фотошопе и, в итоге, у меня получилась вот такая клякса:
Рис. 2 – Моя клякса
Мои дальнейшие планы
В первую очередь, я хочу все ж таки добавить предварительное разбитие исходного кода на токены. А во вторую очередь, я хочу развивать свою библиотеку и дальше,
Потом, я хочу пойти в сторону развития функционала по редактированию стилей и поиску блоков в коде MSS из бэкэнда. Например, можно добавить поддержку условных комментариев IE прямо в исходном файле (не знаю как вас, но меня всегда раздражало, что патчи для IE нужно сохранять и включать на страницу в виде отдельных файлов, тем более, если это всего полтора CSS-правила).
Если у кого-то есть какие-то ещё идеи по совершенствованию моего проекта, я всегда буду рад их услышать.
Вместо заключения
Ну вот, наверное, и всё, что я хотел бы рассказать в своей первой статье про мою библиотеку. Думаю, я смог немного прорекламировать мой продукт. Еще хотелось, так сказать, зафиксировать точку в разработке и получить отзывы о уже проделанной работе.
Буду благодарен вам за возможные советы и рекомендации, а также за всевозможную поддержку и просто теплые слова.
Если вам понравилась моя статья, то я обязательно буду писать ещё.
Ссылки
GitHub: https://github.com/Dobby007/mysheet
Оффициальный сайт: http://mss.flydigo.com/
Документация: http://mss.flydigo.com/docs
P.S. Я извиняюсь, но как кто-то заметил в комментариях — мой хостинг не выдержал напора. Про это я даже и подумать не мог. Сейчас попробуем что-нибудь сделать с этим…
P.P.S. К сожалению, у моего хостинга, оказывается, драконовские ограничения на сайты (причем не только бесплатные). А на запросы они отвечают ровно один раз в китайскую пасху, поэтому придется мне посмотреть в сторону других возможных решений для содержания моего сайта.
P.P.P.S. Доступ к сайту будет полностью восстановлен после обновления всей цепочки DNS. Сейчас сайт доступен по адресу: dobby007_h5a5nu.radius-host.net
Комментарии (64)
twentyfivesymbolsusername
25.04.2015 12:14+8Название на слух какое-то двусмысленное :) Это было так задумано?
pepelsbey
25.04.2015 13:02+7Вы же знаете о существовании Rework, Gonzales и PostCSS? Просто на всякий случай. Даже при написании велосипеда стоит оценивать конкурентов или просто аналогичные существующие решения.
Следующий вопрос по префиксам. Эти примеры говорят о том, что всё плохо:
@mixin filter-grayscale(percent) -webkit-filter: grayscale($percent); -ms-filter: grayscale($percent); -o-filter: grayscale($percent); filter: grayscale($percent); @mixin border-radius(topleft, topright) -webkit-border-radius $topleft $topright 4px 5px border-radius $arguments html { -ms-width: 50px !important; -moz-width: 50px !important; width: 50px !important; }
Несуществующее свойство-o-filter
,-webkit-border-radius
в 2015 году,-ms-width
? Писать префиксы руками сегодня — это очень странное решение. Но то, что вы их выносите в абстракцию, ничего, по сути, не меняет — они всё равно написаны руками. Префиксы нужно добавлять только а) существующие и б) на основе статистики — и это давно уже делает Автопрефиксер. То есть префиксы вообще не нужно писать, нужно только пропускать через него CSS-файл на выходе — он сам знает что нужно, что актуально и как правильно на основе базы Can I use. И конкурировать с этим, даже в рамках велосипеда, я бы не стал.Dobby007 Автор
25.04.2015 13:46-3Собственно говоря, во встроенной версии примеси filter-grayscale нет свойства -o-filter. И делал я его как раз на основе базы caniuse. Вы можете проверить это, удалив определение mixin'а из исходного кода. -ms-width был тоже придуман примера ради. А хотя, кто его знает — может быть IE поймет :)
Автоматическое проставление префиксов не всегда спасает. Иногда нужно написать заплатку под конкретный браузер. Для того примеси и нужны. Например, Firefox начал поддерживать фильтры только с 35 версии. И чтобы сделать изображение черно-белым в нем, нужно включать svg-картинку.dshster
25.04.2015 14:40> Автоматическое проставление префиксов не всегда спасает. Иногда нужно написать заплатку под конкретный браузер
Автопрефиксер в настройках позволяет перечислить нужные браузеры, хоть самые устаревшиеvintage
26.04.2015 12:20Автопрефиксер не добавит вам фоллбэки. Поэтому примеси предпочтительней.
Fesor
26.04.2015 13:20а что вы скажите на счет postcss? мне как-то этот подход нравится больше, когда мы просто прописываем пару строчек на JS, который пройдется по всем стилям и, согласно каким-то вашим правилам, добавит кастылей. В итоге у нас чистый код, примиси используются только там где это требуется, и пользователи старых браузеров довольны.
Хотя конечно все это дело вкуса. Но некоторые фэлбэки крайне не удобно делать через миксины, код стилей усложняется и поддерживать подобное бывает тяжко. Потому мне лично нравится именно тот подход, который я описал.Dobby007 Автор
26.04.2015 16:09Да. Автоматическое проставление браузерных префиксов — вещь удобная. Здесь спору нет. Я хочу это хорошенько обдумать и добавить в мой препроцессор. Но нужно хорошо провести грань между примесями и автопрефиксизацией — когда необходимо использовать одно, а когда другое.
Я даже, честно говоря, не думаю, что нужно усложнять эту функцию правилами типа "> 0.2%" и т.п. Я склоняюсь к тому мнению, что в стилях для сайта нужно учитывать как можно больше браузеров и нужно делать стили совместимыми с большинством из них. Пусть это будет 0.1% посетителей вашего сайта — зато этот 0.1% не закроет вашу страницу после того как увидит что-то несуразное.Fesor
26.04.2015 16:18Мне нравится в postcss как раз таки противоположное — по умолчанию оно ничего не делает. А далее вы просто добавляете плагины, правила и т.д. и можно сделать хоть свой sass/less. А можно взять простенький препроцессор с поддержкой миксинов (я не отрицаю что они нужны, но я из обычно применяю не для хаков/префиксов, а для минимизации какой-то рутины. Спратять какой-нибудь страх и ужас. Ну и комбинировать поведение.
Лучше подумайте над тем, как добавить возможность расширять синтаксис вашего препроцессора без необходимость ковыряться в тоннах кода. Скажем node-visitor-ы свои добавлять и т.д.
vintage
26.04.2015 19:43Ну так Stylus так и работает: вы пишете «чистый код», который примесями разворачивается в лапшу — где-то просто префиксы добавляются, где-то транслируется в разные нотации, а где-то вообще разные правила вставляются. При этом вы не скованы синтаксисом CSS, а можете использовать всякие клёвые штуки типа вложенных правил, вложенных media-queries и прочего.
Iskin
26.04.2015 19:14Для фоллбэков есть другие плагины для PostCSS, например, CSS Grace: github.com/cssdream/cssgrace
vintage
26.04.2015 19:53-1Проблема фолбэков в их неуниверсальности. В разных проектах нужны разные виды фолбэков и многие из них специфичны для конкретных проектов. Например, на одном из проектов мне надо было реализовать прозрачность. Но для браузеров её не поддерживающих (старые версии ие) нельзя было применять фильтры, а необходимо было использовать упрощённый дизайн без прозрачности, что было не так красиво (фоновая картинка, если она была установлена, не просвечивала), зато не тормозило.
Iskin
26.04.2015 19:55+1Всё верно, именно поэтому фоллбэки не часть Автопрефиксера, так как у него нет API, чтобы их отменять. Но у остальных плагинов PostCSS такой API бывает.
pepelsbey
26.04.2015 01:54+3Меня всегда удивляли эти «примера ради», люди ведь копируют код не глядя, забывая читать описания. И потом вы видим
<div class="button">
и прочие безумства. Так что осторожнее. А что касается Автопрефиксера, он позволяет гибко указывать любые целевые версии браузеров и, если нужно, форсить прямо в коде нужные заплатки и префиксы, почитайте об этом в документации.
imgen
25.04.2015 16:39+1hostinger не выдержал напора :)
Dobby007 Автор
25.04.2015 18:14Я даже и не думал, что я в ближайшее время вылезу за пределы их ограничений. Придется, что-нибудь с этим сделать… Только пока что непонятно что…
Fedot
25.04.2015 18:21У гитхаба есть возможность хостить статические сайты.
Dobby007 Автор
25.04.2015 18:29Да, но, к сожалению, у меня сайт не статический и поэтому пришлось искать что-то более подходящее.
Fedot
25.04.2015 19:15Ясно. Обычно у таких вещей сайты статические. Возможно имеет смысл подумать об отказе от динамики в пользу статики. А пока взять какую нибудь не дорогую VDS.
Dobby007 Автор
25.04.2015 22:02Я делал сайт для демонстрации возможностей библиотеки, а это не сделаешь со статическим контентом. На моем текущем сайте можно протестировать библиотеку, а также увидеть «арифметику цветов» в боевом действии.
Fesor
25.04.2015 22:15-1Мне кажется вы не верно интерпретируете фразу «статический сайт». Можно было бы конвертацию вашего Г (простите, не удержался от дословного перевода) в css средствами браузера организовать.
p.s. поздравляю, у вас велосипед.Dobby007 Автор
25.04.2015 22:35-4Мне даже вам сказать нечего. Вы даже статью не прочитали, а делаете выводы. Бог простит
Posia
25.04.2015 17:50+5Ну, конечно же, мне пришлось написать небольшой сайтик для презентации своего продукта (вы уже догадались, какой язык программирования был выбран мной для этой цели?).
Превышен Лимит Процессорной Памяти
Отличный выбор :)Dobby007 Автор
25.04.2015 21:25Доступ к сайту будет полностью восстановлен после обновления всей цепочки DNS. Сейчас сайт доступен по адресу: http://dobby007_h5a5nu.radius-host.net
Jabher
26.04.2015 10:39Но… Но… Но… зачем??
После двухдневного (а может и меньшего) просмотра результатов с гугла, я ничего интересного для себя не нашел.
Я, конечно, прощу прощения, но… ЧТО именно не нашлось интересного, что сподвигло писать свой компилятор?Dobby007 Автор
26.04.2015 11:08-1Во-первых, мне было интересно написать компилятор самому. Во-вторых, мне позарез как нужно было что-то, что позволило бы мне пройтись по файлику стилей и изменить семейство шрифтов на Arial для селектора body и цвет шрифта на #333. И желательно было бы не плодить технологии, а сделать все на родном PHP.
Iskin
26.04.2015 19:13+1border-radius
с префиксами в примере — полный ужас :).
Ну и какой смысл сейчас делать препроцессоры, когда уже во всю развиваются постпроцессоры ;).
POPSuL
28.04.2015 16:52А что на счет иерархичности?
Dobby007 Автор
28.04.2015 18:49Вы имеете в виду древовидную структуру селекторов? Тогда, да. В MySheet есть данная возможность. В статье есть пример с этой функцией. Для наглядности приведу еще один:
html height 100% body color #777 height 100% font-family: 'Open Sans', sans-serif; .wrapper position relative min-height 100% #header color #fff background-color rgba(0, 0, 0, 60%) #logo float left .title padding 4px 5px font-weight bold font-size 14pt #main-menu overflow hidden ul float right li float left padding 8px 6px
компилируется в следующий CSS-код:
html { height: 100% } html body { color: #777777; height: 100%; font-family: "Open Sans",sans-serif } html body .wrapper { position: relative; min-height: 100% } html body .wrapper #header { color: #ffffff; background-color: rgba(0, 0, 0, 0.6) } html body .wrapper #header #logo { float: left } html body .wrapper #header #logo .title { padding: 4px 5px; font-weight: bold; font-size: 14pt } html body .wrapper #header #main-menu { overflow: hidden } html body .wrapper #header #main-menu ul { float: right } html body .wrapper #header #main-menu ul li { float: left; padding: 8px 6px }
Fesor
28.04.2015 18:58html body .wrapper #header #main-menu ul li
я надеюсь что это только для примера и в реальной верстке подобного не будет.Dobby007 Автор
29.04.2015 05:07-1Вы знаете?.. Я так всегда пишу :) И этот код сейчас с моего сайта. И это работает!
Fesor
29.04.2015 10:22+1почитайте про БЭМ, про smacss и т.д… В конце концов разберитесь как браузер селекторы применяет (для каждого элемента справа на лево, так что указывая html в селекторах вы буквально заставляете браузер для каждого элемента все траверсить вверх по DOM. При том что у вас где-то по середине айдишник который уже должен быть только один на странице, и дальше писать вложенности смысла нет.
Dobby007 Автор
29.04.2015 15:38Я согласен насчет производительности. Даже не думал про нее. Я подумал, вы про именование и формат говорите, поэтому сказал, что всегда так пишу. Без препроцессоров это просто нереально поддерживать. Я никогда так не делаю, если пишу на чистом CSS.
Dobby007 Автор
29.04.2015 05:07А что вам не понравилось?
Jabher
29.04.2015 08:02+1попробуйте погуглить CSS performance optimisations. Узнаете много интересного.
Fesor
29.04.2015 10:19Да дело даже не столько в производительности, сколько в излишней специфичности селекторов. Это как минимум не разумно и я не представляю как такую верстку поддерживать.
Dobby007 Автор
29.04.2015 15:33На уровне исходных стилей поддерживать легко. Переносишь блок стилей на уровень корневого элемента и получаешь совсем другие конечные селекторы. А на уровне HTML поддерживать точно также как и все остальное. Эти вещи поддерживать мало влияют друг друга в случае использования препроцессоров, поддерживающих древовидную структуру.
Fesor
29.04.2015 16:09получаешь совсем другие конечные селекторы.
проблемы со специфичностью селекторов это не решает.
Что вы скажите о БЭМ как о другой крайности?Dobby007 Автор
04.05.2015 17:21Вы ошибаетесь. Это напрямую влияет на специфичность селекторов.
О БЭМ я думаю как о решении, разработанном конкретной компанией под их задачи, с которыми мне еще не приходилось сталкиваться. Это нестандартный взгляд на вещи со своими плюсами и минусами.Fesor
04.05.2015 19:06Это напрямую влияет на специфичность селекторов.
Специфичность селекторов и есть проблемы в долгосрочной перспективе.
Это нестандартный взгляд на вещи со своими плюсами и минусами.
Рекомендую к просмотру доклад Вадима Макеева о различных методологиях верстки и про БЭМ в частности. Может будет интересно.
Jabher
29.04.2015 18:44+1мне стало плохо, когда я увидел два айдишника подряд. с еще тремя селекторами перед ними.
POPSuL
29.04.2015 01:58Да, именно это я и имел ввиду. Спасибо за пояснение. Как-то пропустил этот участок в статье.
А есть ли поддержка «ссылок» на «текущий селектор» в дереве, как в less? Например:
body { a { color: red; &:visited { color: blue; } .some-parent & { font-weight: bold; } } }
Dobby007 Автор
29.04.2015 05:21Да. Только не на текущий, а на родительский селектор. Вот только ваш код моя библиотека не переваривает из-за неравномерно расставленных пробелов в строке. Будем разбираться. Попробуйте пока этот:
body { a { color: red; &:visited { color: blue; } .some-parent & { font-weight: bold; } } }
Dobby007 Автор
29.04.2015 05:26На mss.flydigo.com есть блок Try it now внизу страницы. Попробовать ввести свой код можете там.
a553
Torm
да, согласен, чем не устроил синтаксис less: lighten(#a50c5b, 10%)?
Dobby007 Автор
Ну вот вы захотите одновременно добавить яркости и подмешать красного цвета и у вас получится конструкция make_redder(lighten(#aaa, 10%), 10%). А так: #aaa + 10lt + 40r. По мне, такие выражения дают больше возможностей. Но, впрочем, для простых операций можно и функции сделать.
brainunit
Ваш синтаксис нарушает правило перестановки слагаемых, поэтому функции предпочтительнее.
vintage
А зачем тут коммутативность?
Dobby007 Автор
При том можно даже сделать синтаксис следующего вида: «rgb(10, 10, 10) + rgb(10, 10, 19) + red(10) — hue(20) — 20light» и будет тогда та самая коммутативность, не мозолящая глаз. А вообще, я считаю, что это можно рассматривать как неявное преобразование типов в арифметических выражениях в C++. Вы же не задумываетесь, что будет если написать следующее в Си: auto result = 3.5 + 2 + 3 — 2. Хотя на самом деле то компилятор в таком выражении учитывает приоритет двух операндов и приводит один из них к типу с высшем приоритетом. Так и здесь то же самое.
a553
3.5 + 2 + 3 == 3 + 2 + 3.5
Но:
#000 + 10r + 10lt != #000 + 10lt + 10r
Это ещё можно было бы сделать через символ умножения, если бы вы делали корректную операцию изменения яркости, а не линейную, но в текущем состоянии это очень и очень плохое решение.
А ещё у вас
hsla(45, 60%, 20%, 1) + 10lt
не работает. Иrgb(1,1,1) + rgb(1,1,1)
тоже.Dobby007 Автор
А что же вы хотели получить? Одинаковый цвет? Вы суммируете каналы из разных форматов цвета, поэтому и получаете разные результаты. Попробуйте сделать эти же операции в той же последовательности на hslpicker.com и вы получите ровно то же самое.
Зато сравните результаты со следующими выражениями: #000 + 10r + 10g и #000 + 10g + 10r. Они будут одинаковыми.
Я так понимаю вам смущает знак "+"?
Такое и не будет работать, так как я реализовывал только суммирование цвета и какого-либо одного канала.
Это глюки библиотеки MrColor, которые я пока что не успел преодолеть.