【问题标题】:Adding rules to a dynamically created stylesheet inside a shadowDOM向 shadowDOM 中动态创建的样式表添加规则
【发布时间】:2020-01-16 06:49:33
【问题描述】:

我最近开始接触 WebComponents,我必须说到目前为止我发现它具有开创性!为了更好地理解这个概念,我尝试将我早期项目中的设计融入可重用的 Web 组件中。我首先尝试创建一个带有一些附加功能的 Table 元素。

首先,我为组件声明了一个最小模板:

<template id="data-table-template">
  <table>
   <thead></thead>
   <tbody></tbody>
   <tfoot></tfoot>
  </table>
</template>

我使用此模板通过将表头值和行值作为属性来创建表。该元素的使用方式如下:

<eur-data-table
  headers="getHeaders()",
  rowdata="getRowData()"
  host="#data-table-host"
  template="#data-table-template"
></eur-data-table>

在组件原型connectedCallback 方法中,我为组件添加标题、行和设置样式。 Chrome Element 检查器生成的 shadow DOM 片段如下:

<eur-data-table headers="getHeaders()" ,="" rowdata="getRowData()" host="#data-table-host" template="#data-table-template" theme="firebrick">
<table>
 <style id="styleBlock"></style>
 <thead>
     <th>Name</th>
     <th>Age</th>
 </thead>
 <tbody>
   <tr>
     <td>Babu</td>
     <td>60</td>
   </tr>
   <tr>
     <td>Shyam</td>
     <td>35</td>
   </tr>
 </tbody>
 <tfoot></tfoot>
</table>
</eur-data-table>

问题在于添加样式规则。在组件类中,我创建了一个 style 元素并将其附加到影子 DOM,然后再将规则添加到 style.sheet 属性。我检查了style.sheet 的控制台输出,看起来所有规则都已添加。但是,在将样式元素插入到 shadow DOM 之后,从上面渲染的输出中可以看出没有任何规则。

在组件类中,样式的添加方式如下:

connectedCallback() {
        let template = document.querySelector(DataTable._parseId(this.template));
        if(!template) throw new DOMException('Host and/or template ids are undefined');
        if(!this.hasAttribute('theme')) this.setAttribute('theme', 'firebrick');

        let style = document.createElement('style');
        style.setAttribute('id', 'styleBlock');
        // WebKit Hack
        style.appendChild(document.createTextNode(""));
        this.shadowRoot.appendChild(style);

        this._table = template.content.cloneNode(true);
        this._setupHeaders();
        this._setupBody();
        this._setupStyle(style.sheet); // style is not being applied!!
        console.dir(style.sheet); // logs a CSSStyleSheet object
        this.shadowRoot.appendChild(this._table);
}

connectedCallback方法中,_setupStyle被传递一个对样式表的引用,它以如下方式添加规则(规则表示为javascript对象):

_setupStyle(sheet) {
   if(!sheet) throw new DOMException('sheet is undefined', 'StylesheetNotFoundException');
   let rules = window.themes[this.theme];
   if(!rules) throw new DOMException('Theme is undefined');

    Object.keys(rules)
       .sort((r1, r2) => rules[r1].order - rules[r2].order)
       .forEach(rule => {
           let o = _.omit(rules[rule], 'order');
           let i = rule.order;
           let s = this._makeRule(o, rule);
           sheet.insertRule(s, i);
       });    
}

order 值是一个整数,用于按顺序对样式规则进行排序。排序后,order 键及其值在将其转换为 DOMString 之前从对象中省略:

_makeRule(value, rule) {
    return `${rule} ${JSON.stringify(value).replace(new RegExp(/,/g), ';')}`;
}

记录style.sheet 属性打印:

CSSStyleSheet
cssRules: CSSRuleList
0: CSSStyleRule {selectorText: "tfoot", style: CSSStyleDeclaration, styleMap: StylePropertyMap, type: 1, cssText: "tfoot { }", …}
1: CSSStyleRule {selectorText: "tr:last-child > td", style: CSSStyleDeclaration, styleMap: StylePropertyMap, type: 1, cssText: "tr:last-child > td { }", …}
2: CSSStyleRule {selectorText: "td:last-child", style: CSSStyleDeclaration, styleMap: StylePropertyMap, type: 1, cssText: "td:last-child { }", …}
3: CSSStyleRule {selectorText: "td", style: CSSStyleDeclaration, styleMap: StylePropertyMap, type: 1, cssText: "td { }", …}
4: CSSStyleRule {selectorText: "tr:first-child", style: CSSStyleDeclaration, styleMap: StylePropertyMap, type: 1, cssText: "tr:first-child { }", …}
5: CSSStyleRule {selectorText: "tr:last-child", style: CSSStyleDeclaration, styleMap: StylePropertyMap, type: 1, cssText: "tr:last-child { }", …}
6: CSSStyleRule {selectorText: "tr", style: CSSStyleDeclaration, styleMap: StylePropertyMap, type: 1, cssText: "tr { }", …}
7: CSSStyleRule {selectorText: "th:last-child", style: CSSStyleDeclaration, styleMap: StylePropertyMap, type: 1, cssText: "th:last-child { }", …}
8: CSSStyleRule {selectorText: "th", style: CSSStyleDeclaration, styleMap: StylePropertyMap, type: 1, cssText: "th { }", …}
9: CSSStyleRule {selectorText: "thead", style: CSSStyleDeclaration, styleMap: StylePropertyMap, type: 1, cssText: "thead { }", …}
10: CSSStyleRule {selectorText: "thead tbody tfoot", style: CSSStyleDeclaration, styleMap: StylePropertyMap, type: 1, cssText: "thead tbody tfoot { }", …}
11: CSSStyleRule {selectorText: "table", style: CSSStyleDeclaration, styleMap: StylePropertyMap, type: 1, cssText: "table { }", …}

我的问题是:由于组件渲染时没有错误,而且我按预期添加所有规则并将样式和表格元素附加到影子 DOM 中,然后按预期呈现元素,为什么不是我的CSS 规则被应用于渲染的内容?

【问题讨论】:

    标签: css web-component shadow-dom


    【解决方案1】:

    好的,我终于找到了一个可行的解决方案。在样式标签的innerHTML 属性中添加样式要容易得多。为此,我需要将 _setupStyle_makeRule 方法(发布在问题中)更改为以下内容:

    _setupStyle(style) {
       if(!style) throw new DOMException('sheet is undefined', 'StylesheetNotFoundException');
       let rules = window.themes[this.theme];
       if(!rules) throw new DOMException('Theme is undefined');
    
       style.innerHTML = "";
       Object.keys(rules)
             .sort((r1, r2) => rules[r1].order - rules[r2].order)
             .forEach(rule => {
                 let o = _.omit(rules[rule], 'order');
                 let i = rule.order;
                 let s = this._makeRule(o, rule);
                 //sheet.insertRule(s, i);
                 style.innerHTML += s + ' \n';
              });    
    }
    

    我不再使用CSSStyleSheet 类添加规则(已注释掉);相反,我只是在新行上添加每个规则。 _makeRule 方法如下:

    _makeRule(value, rule) {
       return `${rule} ${JSON.stringify(value).replace(this._cssParseRule, ';').replace(/\'|\"/g, "")}`;
    }
    

    我修改了将对象转换为字符串的方法,然后用;替换,,然后替换所有'"。渲染输出如下:

    <style id="styleBlock">
       table {border:1px solid firebrick;border-collapse:collapse;border-spacing:0;padding:0} 
       thead tbody tfoot {margin:0;padding:0} 
       thead {border-bottom:1px solid firebrick;background-color:firebrick;color:azure} 
       th {border-right:1px solid firebrick;border-bottom:1px solid firebrick} 
       th:last-child {border-bottom:none} 
       tr {margin:0;border-bottom:1px solid firebrick} 
       tr:last-child {border-bottom:none} 
       tr:first-child {border-left:none} 
       td {margin:0;padding:10px;text-align:center;border-left:1px solid firebrick;border-right:1px solid firebrick;border-bottom:1px solid firebrick} 
       td:last-child {border-right:none} 
       tr:last-child > td {border-bottom:none} 
       tfoot {margin:0;border-top:1px solid firebrick} 
    </style>
    

    注意:虽然 CSS 可以正常工作,但它是不正确的;每个规则的最后一个属性的末尾应该有一个;

    【讨论】:

      【解决方案2】:

      暂时,直到shadow parts api 可用(这将允许我们将整个元素声明为可由消费者设置样式)

      我建议使用大量 custom css properties(样式挂钩)来为消费者提供影响 Web 组件样式的方法

      出于主题的目的,可能对提议的属性adoptedStyleSheets 提供了一些 polyfill 支持,但我只看到在某些线程中随便提到它,所有这些都像谣言一样

      【讨论】:

      • 如果是这样的话,那就有问题了。由于 shadow DOM 提供的封装,样式规则使用元素的名称作为选择器。我知道自定义属性会泄漏到影子 DOM 中,它会允许客户端影响样式。但是,正如现在设置的那样,它会产生问题,即与其他规则的名称冲突(除非我找到一种方法将所有规则都设为唯一的类)。所以,只是想知道是否有任何机会允许我在组件类中添加规则。
      猜你喜欢
      • 2022-06-11
      • 2017-11-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-14
      • 1970-01-01
      • 2013-05-06
      • 1970-01-01
      相关资源
      最近更新 更多