【问题标题】:Nested themable components: apply styles depending on a theme class of the nearest themed parent, ignoring deeper parents bearing theme classes嵌套主题组件:根据最近的主题父级的主题类应用样式,忽略承载主题类的更深的父级
【发布时间】:2020-12-08 21:01:16
【问题描述】:

我有可以主题化的组件:

<div class="ComponentFoo theme-blue">
</div>

组件可以相互嵌套。

我希望将应用到父组件的主题传播到其所有子组件

一个简单的实现可能如下所示:

<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 采用蓝色主题,因为最近的主题父级是蓝色。但事实并非如此!

这是因为 .theme-blue .ComponentBaz.theme-red .ComponentBaz 选择器都匹配 Baz 组件。 CSS 不关心嵌套的深度。

当两个选择器匹配时,重要的是 CSS 代码中的声明顺序:最后一个获胜。 ????


我可以想象通过以下方式解决此问题:

  1. 使用大量且冗长的选择器利用 &gt; 父组合符和覆盖 CSS 特定性的东西,以便 .theme-red &gt; * 胜过 .theme-red &gt; * &gt; * 等。

    我不喜欢这个解决方案,因为它会使 CSS 不可读。

  2. 使用编程/模板将父主题传递给所有子主题:

    <ComponentFoo @theme="red" as |theme|>
      <ComponentBar @theme={{theme}}>
        <ComponentBaz @theme="blue" as |theme2|>
          <ComponentQuux @theme={{theme2}}/>
        </ComponentBaz>
      </ComponentBar>
    </ComponentFoo>
    

    我不喜欢这个解决方案,因为它也很冗长并且引入了太多的耦合。

  3. 只需将主题 HTML 类显式应用于每个可主题化的组件。

    这就是我正在做的,但我不认为这是一个解决方案。更像是一种变通方法,是一种减少弊端的方法。


有哪些更优雅的方法可以实现这一目标?我想要一个纯 CSS 解决方案,它可以让我在父级上使用 HTML 类,以便它将样式应用于子级并覆盖祖父母的样式。

由于 CSS 非常有限,我们使用的是 Sass 预处理器。如果使用 Sass 非常优雅地抽象出来,我不介意使用产生混乱 CSS 的解决方案。

【问题讨论】:

    标签: css sass css-selectors themes css-specificity


    【解决方案1】:

    我认为您在 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>

    当然,您可以添加另一个类,“themable”,并将选择器与 .ComponentFoo .ComponentBar .ComponentBaz 更改为“.themable”

    这是如何工作的:

    在 theme-blue 类中,您可以定义 CSS 常量(有些人称之为 CSS 变量)的值。该值按照级联规则传播,因此所有子代和后代都将具有相同的值。但是,和所有的 CSS 继承一样,如果一个孩子重新声明了这个值,那么这个孩子的孩子将继承重新声明的值。

    现在所需要做的就是将标准 CSS 属性设置为这个继承的值,这是使用 var() 语法完成的

    【讨论】:

    • 因为我不知道各种组件会有什么确切的样式和元素。 PS 赞成。
    • @AndreyMikhaylov-lolmaus 如果您不知道要将哪些样式应用于 .theme-* 类元素中的级联元素,您希望如何在不使用样式定义特定类的情况下做到这一点?听起来您想将相对解决方案推向绝对问题。谢谢
    • 你检查过我的第二个 sn-p 了吗?我想知道它是否能解决您的问题。
    • 天啊,是的!但我不明白怎么做。为什么这对 CSS 变量的工作方式与实际样式不同?请提供详细的解释,我会接受你的回答!! ??‍♀️
    【解决方案2】:

    通过您的问题,我认为您想选择一个孩子而不是孙子。为此,在 CSS 中我们有 &gt; 选择器。

    例如:

    .test_class_1 > .test_class_2
    

    这里它将选择 test_class_2 类的孩子,但不会选择 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;
    }
    

    在这里我添加了&gt;,它选择带有ComponentBaz 的div 作为一个类,但不是这个div 中的div。我也用“;”替换了“,”也许这只是错字。

    这里是 JSfiddle 链接:https://jsfiddle.net/o20dLy7k/

    【讨论】:

    • 有没有办法调整这个解决方案以允许任意嵌套?那么嵌套较浅的样式会胜过嵌套较深的样式吗?
    【解决方案3】:

    如果您可以将css 限制为主题background、组件colorborder,那么您很幸运。
    一切都可以简化为 2 种颜色——在我的 sn-p 中,第一张图 lblue 和 black——只有 3 种微妙的情况,它们都是微妙的,只是因为文本的可见性:

    • 主题浅蓝色 + 组件浅蓝色(或黑色和黑色)
    • 继承浅蓝色主题+组件浅蓝色
    • 主题浅蓝色 + 继承组件浅蓝色

    不用再担心了。说真的,一切都很有魅力,validator 是绿色的,没有 cmets。
    第一张图 - 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}
    

    如果您可以排除太亮的颜色 - 就是这样。如果不是……例外……
    我将此添加到 sn-p,.Cwhite::before 除外

    【讨论】:

      【解决方案4】:

      您需要确定您的意思。如果您指的是传统意义上的主题,这些解决方案将引入一个难以发现的错误!。如果多个规则匹配,则规则之间不共同的所有属性都将适用。例如,想象一个深色主题改变前景和背景,而霓虹灯主题改变前景和调色板。无论顺序如何,背景(来自黑暗)和调色板(来自霓虹灯)都将适用。

      一种解决方案是规范您的主题,以便每个主题设置由任何主题设置的所有属性。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-08-20
        • 1970-01-01
        • 2020-05-07
        • 2013-07-13
        • 2012-09-18
        • 2016-12-28
        • 2016-01-04
        • 2015-09-29
        相关资源
        最近更新 更多