嵌套主题组件:根据最近主题 parent 的主题 class 应用样式,忽略更深层次的 parents 轴承主题 classes
Nested themable components: apply styles depending on a theme class of the nearest themed parent, ignoring deeper parents bearing theme classes
我有可以主题化的组件:
<div class="ComponentFoo theme-blue">
</div>
组件可以相互嵌套。
我希望将应用于 parent 组件的主题传播到其所有 children.
一个天真的实现可能是这样的:
<div class="ComponentFoo theme-blue">
<div class="ComponentBar">
<div class="ComponentBaz">
</div>
</div>
<div class="ComponentBar">
</div>
<div class="ComponentBar">
</div>
</div>
.theme-blue.ComponentFoo,
.theme-blue .ComponentFoo {
background-color: blue;
}
.theme-blue.ComponentBar,
.theme-blue .ComponentBar {
color: white;
}
这在简单的情况下效果很好,但在嵌套主题时会失败:
<div class="ComponentFoo theme-red">
<div class="ComponentBar theme-blue">
<div class="ComponentBaz">
</div>
</div>
</div>
.theme-blue.ComponentBaz,
.theme-blue .ComponentBaz {
border-color: blue,
color: blue,
background-color: white;
}
.theme-red.ComponentBaz,
.theme-red .ComponentBaz {
border-color: red,
color: red,
background-color: white;
}
在这种情况下,我预计 ComponentBaz
会采用蓝色主题,因为最近的主题 parent 是蓝色的。但事实并非如此!
这是因为 .theme-blue .ComponentBaz
和 .theme-red .ComponentBaz
选择器都匹配 Baz 组件。 CSS不关心嵌套深度
当两个选择器匹配时,重要的是 CSS 代码中声明的顺序:最后一个获胜。
我可以想象通过以下方式解决这个问题:
利用 >
parent 组合子和某些东西来覆盖 CSS 特性,使用极其大量和冗长的选择器,以便 .theme-red > *
胜过 .theme-red > * > *
,等等
我不喜欢这个解决方案,因为它会使 CSS 不可读。
使用 programming/templating 将 parent 的主题传递到其所有 children:
<ComponentFoo @theme="red" as |theme|>
<ComponentBar @theme={{theme}}>
<ComponentBaz @theme="blue" as |theme2|>
<ComponentQuux @theme={{theme2}}/>
</ComponentBaz>
</ComponentBar>
</ComponentFoo>
我不喜欢这个解决方案,因为它也很冗长并且引入了太多耦合。
只需将主题 HTML class 显式应用到每个主题组件。
这就是我正在做的,但我认为这不是解决方案。更像是一种解决方法,弊端较小。
有哪些更优雅的实现方式?我想要一个纯粹的 CSS 解决方案,它可以让我在 parent 上使用 HTML class,以便将样式应用于 children 并覆盖 grand-parents' 样式。
由于 CSS 非常有限,我们使用 Sass 预处理器。如果使用 Sass.
非常优雅地抽象出来,我不介意使用产生混乱 CSS 的解决方案
我认为您在 CSS 中设置了太多规则。
为什么不只为主题设置选择器,让继承来完成这项工作?
.theme-blue {
border-color: blue;
color: blue;
background-color: white;
}
.theme-red {
border-color: red;
color: red;
background-color: white;
}
div {
border-width: 1px;
border-style: solid;
padding: 4px;
}
<div class="ComponentFoo theme-red">I am red
<div class="ComponentBar theme-blue">I am blue
<div class="ComponentBaz">I am nested
</div>
</div>
</div>
<div class="ComponentFoo theme-blue">I am blue
<div class="ComponentBar theme-red">I am red
<div class="ComponentBaz">I am nested
</div>
</div>
</div>
使用 CSS 常量的替代解决方案
.theme-blue {
--border-color: blue;
--color: blue;
--background-color: white;
}
.theme-red {
--border-color: red;
--color: red;
--background-color: white;
}
.ComponentFoo, .ComponentBar, .ComponentBaz {
border-color: var(--border-color);
color: var(--color);
background-color: var(--background-color);
}
.other {
border-color: black;
color: green;
background-color: silver;
}
div {
border-width: 1px;
border-style: solid;
padding: 4px;
}
<div class="ComponentFoo theme-red">I am red
<div class="ComponentBar theme-blue">I am blue
<div class="other">other
<div class="ComponentBaz">I am nested
</div>
</div>
</div>
</div>
<div class="ComponentFoo theme-blue">I am blue
<div class="ComponentBar theme-red">I am red
<div class="other">other
<div class="ComponentBaz">I am nested
</div>
</div>
</div>
</div>
当然你可以再添加一个class,"themable",把选择器.ComponentFoo .ComponentBar .ComponentBaz改成".themable"
这是如何工作的:
在 theme-blue class 中定义 CSS 常量(有些人称之为 CSS 变量)的值。此值按照级联规则传播,因此所有子项和后代都将具有相同的值。但是,与所有 CSS 继承一样,如果子项重新声明此值,则该子项的子项将继承重新声明的值。
现在所需要做的就是将标准 CSS 属性设置为此继承值,这是使用 var() 语法完成的
通过你的问题,我认为你想要 select child 而不是盛大的 child。为此,在 CSS 中我们有 >
select 或。
例如:
.test_class_1 > .test_class_2
这里会 select child 和 class test_class_2
但不会 select 里面的 grandchild test_class_2
div.
据此,我稍微修改了你的css:
.theme-blue>.ComponentBaz,
.theme-blue>.ComponentBaz {
border-color: blue;
color: blue;
background-color: white;
}
.theme-red>.ComponentBaz,
.theme-red>.ComponentBar {
border-color: red;
color: red;
background-color: white;
}
这里我添加了 >
其中 selects div 和 ComponentBaz
作为 class 但 不是 这个 div 里面的 div。我还将“,”替换为“;”也许那只是打字错误。
这是 JSfiddle link:https://jsfiddle.net/o20dLy7k/
如果您可以将 css
限制为主题 background
、组件 color
和 border
,那么您就走运了。
一切都可以简化为 2 种颜色——在我的片段中,第一张图是蓝色和黑色——只有 3 种微妙的情况,它们都是微妙的,只是因为文本的可见性:
- 主题light-blue+组件light-blue(或黑黑)
- 继承light-blue主题+组件light-blue
- 主题light-blue + 继承组件light-blue
不用担心了。说真的,一切都很好,validator 是绿色的,没有评论。
第一张图 - 2 种颜色 - 对可能的担忧的测试,第二张丑陋但信息丰富 - 各种组合的十几种颜色。我使用 ::before{ content: attr(class)}
作为内容,所以我的 css 看起来比平时差。
我决定,非常、非常复杂的情况——比如在图的中心——不应该修正它们的文本颜色(特殊处理改变了遗留),代码太多。如果你愿意,你可以使用不同于 color
的东西,问题就会消失。
当然,使用的颜色必须是可见的。
function switcher(){
var element = document.getElementById("body");
if(element.classList){ element.classList.toggle("shadow");}
else{
var classes = element.className.split(" ");
var i = classes.indexOf("shadow");
if(i >= 0) classes.splice(i, 1);
else classes.push("shadow");
element.className = classes.join(" ");}}
body{ color: #000}
p, span, h1, h2, h3, h4, h5, h6, div::before{ background: #fff}
/* themes */
.shadow .black{ background: #000; color: #fff}
.black{ background: #000;}
.red{ background: #f00;}
.green{ background: #0d0;}
.blue{ background: #00f}
.white{ background: #fff}
.gold{ background: gold}
.purple{ background: purple}
.grey{ background: grey}
.teal{ background: teal}
.lblue{ background: #5ae}
/* components */
.Cblack{ border-color: black; color: black}
.Cred{ border-color: red; color: red}
.Cgreen{ border-color: green; color: green}
.Cblue{ border-color: blue; color: blue}
.Cwhite{ border-color: white; color: white}
.Cgold{ border-color: gold; color: gold}
.Cpurple{ border-color: purple; color: purple}
.Cteal{ border-color: teal; color: teal}
.Clblue{ border-color: #5ae; color: #5ae}
.Cwhite::before, .Cwhite>::before{ background: #aaa}
/* shadow */
.shadow .lblue .Clblue::before, .shadow .red .Cred::before, .shadow .green .Cgreen::before, .shadow .blue .Cblue::before, .shadow .white .Cwithe::before, .shadow .gold .Cgold::before, .shadow .purple .Cpurple::before, .shadow .grey .Cgrey::before, .shadow .teal .Cteal::before, .shadow .black .Cblack::before,
.shadow .Clblue .lblue::before, .shadow .Cred .red::before, .shadow .Cgreen .green::before, .shadow .Cblue .blue::before, .shadow .Cwhite .withe::before, .shadow .Cgold .gold::before, .shadow .Cpurple .purple::before, .shadow .Cgrey .grey::before, .shadow .Cteal .teal::before, .shadow .Cblack .black::before,
.shadow .lblue.Clblue::before, .shadow .red.Cred::before, .shadow .green.Cgreen::before, .shadow .blue.Cblue::before, .shadow .white.Cwhite::before, .shadow .gold.Cgold::before, .shadow .purple.Cpurple::before, .shadow .grey.Cgrey::before, .shadow .teal.Cteal::before, .shadow .black.Cblack::before{
border: 1px dotted black;
text-shadow: -1px 0 1px black, 0 1px 1px black, 1px 0 1px black, 0 -1px 1px black;}
.shadow .black .Cblack::before, .shadow .Cblack .black::before, .shadow .black.Cblack::before{
border: 1px dotted white;
text-shadow: -1px 0 1px white, 0 1px 1px white, 1px 0 1px white, 0 -1px 1px white}
.shadow ::before, .shadow p, .shadow span, .shadow h4{ background: 0}
/* for snippet only */
button{ position: fixed; top: 45vh; right: 0; background: #acf; border: 12px solid white; color: black; padding: 25px; border-radius: 50%; outline: 0}
div{ margin: 10px 8px; padding: 10px 8px; border: 3px solid transparent;}
div::before{ content: attr(class); border-radius: 8px}
p, span, h1, h2, h3, h4, h5, h6, div::before{ padding: 2px 8px;}
<body id="body" class="x">
<div class="lblue Clblue"><h4>h4</h4><p>paragraph</p><span>span</span>
<div class="Cblack">
<div class="black">
<div class="Cblack">
<div class="Clblue">
<div class="lblue">
<div class="Cblack"><h4>h4</h4><p>paragraph</p><span>span</span>
<div class="Clblue"><h4>h4</h4><p>paragraph</p><span>span</span></div>
<div class="black"><h4>h4</h4><p>paragraph</p><span>span</span>
</div></div></div></div></div></div></div></div>
<div class="red Cgold">
<div class="black">
<div class="black Cblack">
<div class="red Cblack">
<div class="green">
<div class="blue ">
<div class="white ">
</div></div></div>
<div class="gold ">
<div class="purple ">
<div class="grey Cred"><h4>h4</h4><p>paragraph</p><span>span</span>
<div class="teal ">
<div class="white Cwhite">
</div></div></div>
<div class="teal Cteal">
<div class="white Cwhite">
<div class="blue">
<div class="black Cteal">
<div class="grey Cred">
</div></div></div>
<div class="Cpurple"><h4>h4</h4><p>paragraph</p><span>span</span>
<div class="Cred"><h4>h4</h4><p>paragraph</p><span>span</span>
<div class="Cgold"><h4>h4</h4><p>paragraph</p><span>span</span>
</div></div></div>
<div class="green">
<div class="white Cgreen">
<div class="red Cred"><h4>h4</h4><p>paragraph</p><span>span</span>
<div class="Cwhite"><h4>h4</h4><p>paragraph</p><span>span</span>
</div></div></div>
</div></div></div>
</div></div></div>
</div></div></div>
<button onclick="switcher()"><b>switch</b></button></body>
补充
如果你能确定,文本将始终在标签内提供,忘记阴影
p, span, h1, h2, h3, h4, h5, h6{ background: #fff}
如果您可以排除太亮的颜色 - 就是这样。如果不是...例外...
我将此添加到代码段中,.Cwhite::before
除外
你需要确定你的意思。如果您指的是传统意义上的主题,这些解决方案将引入一个很难发现的错误!。如果多个规则匹配,则将应用规则之间不共有的所有属性。例如,想象一个黑暗主题改变前景和背景,霓虹灯主题改变前景和调色板。无论顺序如何,背景(来自黑暗)和调色板(来自霓虹)都会应用。
一种解决方案是规范您的主题,使每个主题设置任何主题设置的所有属性。
我有可以主题化的组件:
<div class="ComponentFoo theme-blue">
</div>
组件可以相互嵌套。
我希望将应用于 parent 组件的主题传播到其所有 children.
一个天真的实现可能是这样的:
<div class="ComponentFoo theme-blue">
<div class="ComponentBar">
<div class="ComponentBaz">
</div>
</div>
<div class="ComponentBar">
</div>
<div class="ComponentBar">
</div>
</div>
.theme-blue.ComponentFoo,
.theme-blue .ComponentFoo {
background-color: blue;
}
.theme-blue.ComponentBar,
.theme-blue .ComponentBar {
color: white;
}
这在简单的情况下效果很好,但在嵌套主题时会失败:
<div class="ComponentFoo theme-red">
<div class="ComponentBar theme-blue">
<div class="ComponentBaz">
</div>
</div>
</div>
.theme-blue.ComponentBaz,
.theme-blue .ComponentBaz {
border-color: blue,
color: blue,
background-color: white;
}
.theme-red.ComponentBaz,
.theme-red .ComponentBaz {
border-color: red,
color: red,
background-color: white;
}
在这种情况下,我预计 ComponentBaz
会采用蓝色主题,因为最近的主题 parent 是蓝色的。但事实并非如此!
这是因为 .theme-blue .ComponentBaz
和 .theme-red .ComponentBaz
选择器都匹配 Baz 组件。 CSS不关心嵌套深度
当两个选择器匹配时,重要的是 CSS 代码中声明的顺序:最后一个获胜。
我可以想象通过以下方式解决这个问题:
利用
>
parent 组合子和某些东西来覆盖 CSS 特性,使用极其大量和冗长的选择器,以便.theme-red > *
胜过.theme-red > * > *
,等等我不喜欢这个解决方案,因为它会使 CSS 不可读。
使用 programming/templating 将 parent 的主题传递到其所有 children:
<ComponentFoo @theme="red" as |theme|> <ComponentBar @theme={{theme}}> <ComponentBaz @theme="blue" as |theme2|> <ComponentQuux @theme={{theme2}}/> </ComponentBaz> </ComponentBar> </ComponentFoo>
我不喜欢这个解决方案,因为它也很冗长并且引入了太多耦合。
只需将主题 HTML class 显式应用到每个主题组件。
这就是我正在做的,但我认为这不是解决方案。更像是一种解决方法,弊端较小。
有哪些更优雅的实现方式?我想要一个纯粹的 CSS 解决方案,它可以让我在 parent 上使用 HTML class,以便将样式应用于 children 并覆盖 grand-parents' 样式。
由于 CSS 非常有限,我们使用 Sass 预处理器。如果使用 Sass.
非常优雅地抽象出来,我不介意使用产生混乱 CSS 的解决方案我认为您在 CSS 中设置了太多规则。
为什么不只为主题设置选择器,让继承来完成这项工作?
.theme-blue {
border-color: blue;
color: blue;
background-color: white;
}
.theme-red {
border-color: red;
color: red;
background-color: white;
}
div {
border-width: 1px;
border-style: solid;
padding: 4px;
}
<div class="ComponentFoo theme-red">I am red
<div class="ComponentBar theme-blue">I am blue
<div class="ComponentBaz">I am nested
</div>
</div>
</div>
<div class="ComponentFoo theme-blue">I am blue
<div class="ComponentBar theme-red">I am red
<div class="ComponentBaz">I am nested
</div>
</div>
</div>
使用 CSS 常量的替代解决方案
.theme-blue {
--border-color: blue;
--color: blue;
--background-color: white;
}
.theme-red {
--border-color: red;
--color: red;
--background-color: white;
}
.ComponentFoo, .ComponentBar, .ComponentBaz {
border-color: var(--border-color);
color: var(--color);
background-color: var(--background-color);
}
.other {
border-color: black;
color: green;
background-color: silver;
}
div {
border-width: 1px;
border-style: solid;
padding: 4px;
}
<div class="ComponentFoo theme-red">I am red
<div class="ComponentBar theme-blue">I am blue
<div class="other">other
<div class="ComponentBaz">I am nested
</div>
</div>
</div>
</div>
<div class="ComponentFoo theme-blue">I am blue
<div class="ComponentBar theme-red">I am red
<div class="other">other
<div class="ComponentBaz">I am nested
</div>
</div>
</div>
</div>
当然你可以再添加一个class,"themable",把选择器.ComponentFoo .ComponentBar .ComponentBaz改成".themable"
这是如何工作的:
在 theme-blue class 中定义 CSS 常量(有些人称之为 CSS 变量)的值。此值按照级联规则传播,因此所有子项和后代都将具有相同的值。但是,与所有 CSS 继承一样,如果子项重新声明此值,则该子项的子项将继承重新声明的值。
现在所需要做的就是将标准 CSS 属性设置为此继承值,这是使用 var() 语法完成的
通过你的问题,我认为你想要 select child 而不是盛大的 child。为此,在 CSS 中我们有 >
select 或。
例如:
.test_class_1 > .test_class_2
这里会 select child 和 class test_class_2
但不会 select 里面的 grandchild test_class_2
div.
据此,我稍微修改了你的css:
.theme-blue>.ComponentBaz,
.theme-blue>.ComponentBaz {
border-color: blue;
color: blue;
background-color: white;
}
.theme-red>.ComponentBaz,
.theme-red>.ComponentBar {
border-color: red;
color: red;
background-color: white;
}
这里我添加了 >
其中 selects div 和 ComponentBaz
作为 class 但 不是 这个 div 里面的 div。我还将“,”替换为“;”也许那只是打字错误。
这是 JSfiddle link:https://jsfiddle.net/o20dLy7k/
如果您可以将 css
限制为主题 background
、组件 color
和 border
,那么您就走运了。
一切都可以简化为 2 种颜色——在我的片段中,第一张图是蓝色和黑色——只有 3 种微妙的情况,它们都是微妙的,只是因为文本的可见性:
- 主题light-blue+组件light-blue(或黑黑)
- 继承light-blue主题+组件light-blue
- 主题light-blue + 继承组件light-blue
不用担心了。说真的,一切都很好,validator 是绿色的,没有评论。
第一张图 - 2 种颜色 - 对可能的担忧的测试,第二张丑陋但信息丰富 - 各种组合的十几种颜色。我使用 ::before{ content: attr(class)}
作为内容,所以我的 css 看起来比平时差。
我决定,非常、非常复杂的情况——比如在图的中心——不应该修正它们的文本颜色(特殊处理改变了遗留),代码太多。如果你愿意,你可以使用不同于 color
的东西,问题就会消失。
当然,使用的颜色必须是可见的。
function switcher(){
var element = document.getElementById("body");
if(element.classList){ element.classList.toggle("shadow");}
else{
var classes = element.className.split(" ");
var i = classes.indexOf("shadow");
if(i >= 0) classes.splice(i, 1);
else classes.push("shadow");
element.className = classes.join(" ");}}
body{ color: #000}
p, span, h1, h2, h3, h4, h5, h6, div::before{ background: #fff}
/* themes */
.shadow .black{ background: #000; color: #fff}
.black{ background: #000;}
.red{ background: #f00;}
.green{ background: #0d0;}
.blue{ background: #00f}
.white{ background: #fff}
.gold{ background: gold}
.purple{ background: purple}
.grey{ background: grey}
.teal{ background: teal}
.lblue{ background: #5ae}
/* components */
.Cblack{ border-color: black; color: black}
.Cred{ border-color: red; color: red}
.Cgreen{ border-color: green; color: green}
.Cblue{ border-color: blue; color: blue}
.Cwhite{ border-color: white; color: white}
.Cgold{ border-color: gold; color: gold}
.Cpurple{ border-color: purple; color: purple}
.Cteal{ border-color: teal; color: teal}
.Clblue{ border-color: #5ae; color: #5ae}
.Cwhite::before, .Cwhite>::before{ background: #aaa}
/* shadow */
.shadow .lblue .Clblue::before, .shadow .red .Cred::before, .shadow .green .Cgreen::before, .shadow .blue .Cblue::before, .shadow .white .Cwithe::before, .shadow .gold .Cgold::before, .shadow .purple .Cpurple::before, .shadow .grey .Cgrey::before, .shadow .teal .Cteal::before, .shadow .black .Cblack::before,
.shadow .Clblue .lblue::before, .shadow .Cred .red::before, .shadow .Cgreen .green::before, .shadow .Cblue .blue::before, .shadow .Cwhite .withe::before, .shadow .Cgold .gold::before, .shadow .Cpurple .purple::before, .shadow .Cgrey .grey::before, .shadow .Cteal .teal::before, .shadow .Cblack .black::before,
.shadow .lblue.Clblue::before, .shadow .red.Cred::before, .shadow .green.Cgreen::before, .shadow .blue.Cblue::before, .shadow .white.Cwhite::before, .shadow .gold.Cgold::before, .shadow .purple.Cpurple::before, .shadow .grey.Cgrey::before, .shadow .teal.Cteal::before, .shadow .black.Cblack::before{
border: 1px dotted black;
text-shadow: -1px 0 1px black, 0 1px 1px black, 1px 0 1px black, 0 -1px 1px black;}
.shadow .black .Cblack::before, .shadow .Cblack .black::before, .shadow .black.Cblack::before{
border: 1px dotted white;
text-shadow: -1px 0 1px white, 0 1px 1px white, 1px 0 1px white, 0 -1px 1px white}
.shadow ::before, .shadow p, .shadow span, .shadow h4{ background: 0}
/* for snippet only */
button{ position: fixed; top: 45vh; right: 0; background: #acf; border: 12px solid white; color: black; padding: 25px; border-radius: 50%; outline: 0}
div{ margin: 10px 8px; padding: 10px 8px; border: 3px solid transparent;}
div::before{ content: attr(class); border-radius: 8px}
p, span, h1, h2, h3, h4, h5, h6, div::before{ padding: 2px 8px;}
<body id="body" class="x">
<div class="lblue Clblue"><h4>h4</h4><p>paragraph</p><span>span</span>
<div class="Cblack">
<div class="black">
<div class="Cblack">
<div class="Clblue">
<div class="lblue">
<div class="Cblack"><h4>h4</h4><p>paragraph</p><span>span</span>
<div class="Clblue"><h4>h4</h4><p>paragraph</p><span>span</span></div>
<div class="black"><h4>h4</h4><p>paragraph</p><span>span</span>
</div></div></div></div></div></div></div></div>
<div class="red Cgold">
<div class="black">
<div class="black Cblack">
<div class="red Cblack">
<div class="green">
<div class="blue ">
<div class="white ">
</div></div></div>
<div class="gold ">
<div class="purple ">
<div class="grey Cred"><h4>h4</h4><p>paragraph</p><span>span</span>
<div class="teal ">
<div class="white Cwhite">
</div></div></div>
<div class="teal Cteal">
<div class="white Cwhite">
<div class="blue">
<div class="black Cteal">
<div class="grey Cred">
</div></div></div>
<div class="Cpurple"><h4>h4</h4><p>paragraph</p><span>span</span>
<div class="Cred"><h4>h4</h4><p>paragraph</p><span>span</span>
<div class="Cgold"><h4>h4</h4><p>paragraph</p><span>span</span>
</div></div></div>
<div class="green">
<div class="white Cgreen">
<div class="red Cred"><h4>h4</h4><p>paragraph</p><span>span</span>
<div class="Cwhite"><h4>h4</h4><p>paragraph</p><span>span</span>
</div></div></div>
</div></div></div>
</div></div></div>
</div></div></div>
<button onclick="switcher()"><b>switch</b></button></body>
补充
如果你能确定,文本将始终在标签内提供,忘记阴影
p, span, h1, h2, h3, h4, h5, h6{ background: #fff}
如果您可以排除太亮的颜色 - 就是这样。如果不是...例外...
我将此添加到代码段中,.Cwhite::before
你需要确定你的意思。如果您指的是传统意义上的主题,这些解决方案将引入一个很难发现的错误!。如果多个规则匹配,则将应用规则之间不共有的所有属性。例如,想象一个黑暗主题改变前景和背景,霓虹灯主题改变前景和调色板。无论顺序如何,背景(来自黑暗)和调色板(来自霓虹)都会应用。
一种解决方案是规范您的主题,使每个主题设置任何主题设置的所有属性。