Владимир Кузнецов, Хантфлоу
Владимир Кузнецов, Хантфлоу
FrontTalks, 2018
Может оказаться, что существующий плагин решает задачу,
но не так как вам хотелось бы.
Что делать?
/* Input */
.block { width: 100%; }
@media (min-width: 720px) {
.block { width: 25%; }
}
/* Output */
.block { width: 100%; }
.block { width: 25%; }
{ from: 'darkslategray', to: '#556832' }
p { color: darkslategray; }
a { color: #2F4F4F; }
.quote {
border-left: 1px solid rgb(47, 79, 79);
border-right: solid hsl(180, 25%, 25%) 1px;
}
{ from: 'darkslategray', to: '#556832' }
p { color: darkslategray; }
a { color: #2F4F4F; }
.quote {
border-left: 1px solid rgb(47, 79, 79);
border-right: solid hsl(180, 25%, 25%) 1px;
}
{ from: 'darkslategray', to: '#556832' }
p { color: #556832; }
a { color: #556832; }
.quote {
border-left: 1px solid #556832;
border-right: solid #556832 1px;
}
Типы узлов
Методы обхода
function plugin(options) {
return function (root) {
root.walkDecls(function (decl) {
if (decl.value === options.from)
decl.value = options.to;
});
};
}
module.exports = postcss.plugin('alter-color', plugin);
Сплошные цвета
Цвета с прозрачностью
{ rgb: [ 255, 165, 0 ],
hsl: [ 39, 100, 50 ],
keyword: 'orange',
hex: '#ffa500',
rgba: [ 255, 165, 0, 1 ],
hsla: [ 39, 100, 50, 1 ] }
#main { border: 1px solid #2F4F4F }
{
type: "decl",
prop: "border",
value: "1px solid #2F4F4F"
}
root.walkDecls(decl => {
const parsedValue = cssTree.parse(
decl.value,
{ context: 'value' }
);
// тут можно модифицировать parsedValue
decl.value = cssTree.generate(parsedValue);
});
"1px solid #2F4F4F"
{ type: "Value", children: [
{ type: "Dimension", value: 1, unit: "px" },
{ type: "WhiteSpace", value: " " },
{ type: "Identifier", name: "solid" },
{ type: "WhiteSpace", value: " " },
{ type: "HexColor", value: "2F4F4F" }
] }
"darkslategray"
{ type: "Value", children: [
{ type: "Identifier", name: "darkslategray" }
] }
rgb(47, 79, 79)
{ type: "Value", children: [
{ type: "Function", name: "rgb", "children": [
{ type: "Number", value: "47"},
{ type: "Operator", value: ","},
{ type: "Number", value: "79"},
{ type: "Operator", value: ","},
{ type: "Number", value: "79"}
] }
] }
function identifierVisitor(node, item, list) {
if (node.name === 'darkslategray') {
// изменяем node
}
}
cssTree.walk(parsedValue, {
visit: 'Identifier',
enter: identifierVisitor
});
function identifierVisitor(node, item, list) {
if (node.name === 'darkslategray') {
const data = cssTree.fromPlainObject({
type: 'HexColor',
value: '556832'
});
list.replace(item, list.createItem(data));
}
}
cssTree.walk(parsedValue, {
visit: 'HexColor',
enter: hexColorVisitor
});
cssTree.walk(parsedValue, {
visit: 'Function',
enter: functionVisitor
});
{ type: "Value", children: [
{ type: "Function", name: "rgb", "children": [
{ type: "Number", value: "47"},
{ type: "Operator", value: ","},
{ type: "Number", value: "79"},
{ type: "Operator", value: ","},
{ type: "Number", value: "79"}
] }
] }
{ type: "Value", children: [
{ type: "Function", name: "rgb", "children": [
{ type: "Percentage", value: "18.4%"},
{ type: "Operator", value: ","},
{ type: "Percentage", value: "30.98%"},
{ type: "Operator", value: ","},
{ type: "Percentage", value: "30.98%"}
] }
] }
Website | Portal | Business | International | ||
---|---|---|---|---|---|
Primary | |||||
Secondary | |||||
Accent |
:root { --primary-color: #E54096; }
.theme-portal { --primary-color: #0080C5; }
.theme-business { --primary-color: #C1D730; }
.theme-international { --primary-color: #E5352D; }
.tile_primary { background: var(--primary-color); }
[ { "name": "primary", "match": "#E54096",
"themes": {
"portal": "#0080C5",
"business": "#C1D730",
"international": "#E5352D"
}
}, { name: "secondary", … } ]
.tile_primary { color: #E54096 }
.theme-portal .tile_primary { color: #0080C5 }
.theme-business .tile_primary { color: #C1D730 }
.theme-international .tile_primary { color: #E5352D }
const plugin = options => (root, result) => {
root.walkRules(rule => {
const config = getConfig(rule, options);
const themed = Object.keys(config.themes)
.map(name => makeThemedRule(rule, name, config));
themed.unshift(rule.clone());
rule.replaceWith(themed);
});
};
function getConfig(rule, options) {
return options.find(config => {
const p = `_${config.name}(:[a-zA-Z]+)?(\\s.*)?$`;
const rx = new RegExp(p);
return rule.selectors.every(s => rx.test(s));
});
}
{
"name": "primary",
"match": "#E54096",
"themes": {
"portal": "#0080C5",
"business": "#C1D730",
"international": "#E5352D"
}
}
const plugin = options => (root, result) => {
root.walkRules(rule => {
const config = getConfig(rule, options);
const themed = Object.keys(config.themes)
.map(name => makeThemedRule(rule, name, config));
themed.unshift(rule.clone());
rule.replaceWith(themed);
});
};
function makeThemedRule(rule, themeName, config) {
const themedRule = rule.clone();
themedRule.selectors = themedRule.selectors
.map(selector => `.theme-${themeName} ${selector}`);
const processor = getDeclProcessor(themeName, config);
themedRule.walkDecls(processor);
return themedRule;
}
function getDeclProcessor(themeName, config) {
return decl => {
const ast = cssTree
.parse(decl.value, { context: 'value' });
cssTree.walk(ast, { visit: 'HexColor', … });
cssTree.walk(ast, { visit: 'Function', … });
decl.value = cssTree.generate(ast);
};
}
Модификатор есть, а изменить ничего нельзя!
rule.warn(result, 'Suspicious rule');
.tile_primary .text-tile__heading {
color: #E54096;
}
.tile__badge_primary:hover {
background-color: #E54096;
}
.button_primary[disabled] {
background: lighten( #E54096, 20%); /* #F19AC7 */
}
.overlay_primary {
background: darken( #E54096, 20%); /* #A81763 */
}
Тёмный | Базовый | Светлый |
---|---|---|
hsl(329, 76%, 37%) | hsl(329, 76%, 57%) | hsl(329, 76%, 77%) |
hsl(201, 100%, 19%) | hsl(201, 100%, 39%) | hsl(201, 100%, 59%) |
hsl(68, 68%, 32%) | hsl(68, 68%, 52%) | hsl(68, 68%, 72%) |
hsl(3, 78%, 34%) | hsl(3, 78%, 54%) | hsl(3, 78%, 74%) |
В цветовом пространстве HSL компоненты hue и saturation будут без изменений, а разницу значений компоненты lightness нужно добавить к цвету темы.
#F19AC7 → hsl(329, 76%, 77%) → hsl(329, 76%, 57%) → Δl = 20%
#0080C5 → hsl(201, 100%, 39%) → hsl(201, 100%, 59%) → #2EB6FF
James Steinbach
@mistakster (English)
@mista_k (больше про жизнь)
Слайды презентации: bit.ly/ft18-cm