首页 > 解决方案 > 向 shadowDOM 中动态创建的样式表添加规则

问题描述

我最近开始接触 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 规则不是应用于渲染的内容?

标签: cssweb-componentshadow-dom

解决方案


好的,我终于找到了一个可行的解决方案。innerHTML在样式标签的属性中添加样式要容易得多。为此,我需要将_setupStyleand_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 可以工作,但它是不正确的;;每个规则的最后一个属性的末尾应该有一个。


推荐阅读