正确的方法将全局样式应用于Shadow DOM

22

这个问题与StackOverflow上的一些其他问题类似,但我找不到任何适用于我的情况和非弃用方法的答案(我开始想也许没有任何好的解决方案)。

假设我们有一个包含按钮、列表、链接等通用样式的main.css文件。因此,它只是一个包含我们希望在整个应用程序中重用的通用样式的标准.css文件。而我们希望将相同的样式应用于具有Shadow DOM的Web组件。

据我所知,有几种方法可以实现这一点:

  1. 使用已弃用的方法之一:::shadow,>>>, / deep /选择器。但这些选择器现在已经被弃用了,所以我认为这不是一个往前推进的好方法。
  2. 使用css变量。如果需要设置几个属性,则此方法适用于定制目的。但是,如果我们想要从main.css文件迁移10-20个常用样式,这种方法过于复杂。
  3. 在Shadow DOM内部使用@import语句或“link”标记。它会起作用,但它会为每个组件复制所有样式。如果我们有10个Web组件,最终将得到完全相同的10个副本。这听起来也不像足够好的解决方案。特别是如果我们有很多共同的样式,从性能角度来看可能是不好的解决方案。
  4. 完全不使用Shadow DOM并使用全局样式:) 但这不是当前问题的解决方案。

我还查看了Angular框架中(我查看的是Angular 5版本)如何解决相同的问题。当我将封装行为设置为Native时,它实际上只是复制样式(就像上面第3种描述的那样),我认为这不是最好的方式(但也许是目前存在的最佳方式)。

那么,有没有人知道是否有其他方法可以解决这个问题,而不会出现上述缺点?听起来当前Shadow DOM的缺点带来的问题比它试图解决的问题还要多。

2个回答

21

方案3没有真正的缺点:

  1. 无论您在主文档中将CSS样式应用于n个元素还是在n个 Shadow DOM 中的一个元素上应用,该样式都将被复制到整个n个元素。

  2. 如果您在n个Shadow DOM中导入文档,它实际上只会被加载一次并通过浏览器缓存重复使用。

之后,这将取决于Shadow DOM和CSS样式的浏览器实现,并且只有成千上万的 Shadow DOM 才会看到性能下降。


Chrome 73+和Opera 60+ 的2019年更新

现在,您可以直接实例化一个 CSSStyleSheet 对象并将其分配给不同的 Shadow DOM。

通过这种方式,HTML将不会被复制。

var css = new CSSStyleSheet()
css.replaceSync( "@import url( main.css )" )
host.shadowRoot.adoptedStyleSheets = [css] 
host2.shadowRoot.adoptedStyleSheets = [css] 

你也可以将它应用于全局文档:

document.adoptedStyleSheets = [css]

另一个优点是样式表的更新将应用于所有采用它的Shadow DOM(和文档)。

 css.replaceSync( '.color { color: red }' )

1
值得一提的是,该功能的polyfill已经可用 - Caleb Williams
3
似乎现在针对replaceSync的@import被忽略了。 - Aaron Gong
1
关于解决方案3,没有任何缺点。当我使用global.css文件时,它会被每个使用它的组件获取。因此,当我附加一个具有深度嵌套自定义元素的自定义元素时,我会出现大的闪烁问题。 - J. Doe
@J.Doe,你认为解决方案3有缺陷吗?抱歉,我没有听懂你的评论。 - Supersharp
2
@Supersharp 当我在多个 Shadow DOM 中导入相同的 CSS 文件时,每次都会从服务器获取该 CSS 文件。因此,每当我将包含此 CSS 文件的自定义元素添加到 DOM 中时,它都没有样式,直到获取了 CSS 文件,这会导致未经样式化的 HTML 的“闪烁”。当我有嵌套的 Web 组件时,这个问题变得更加严重。 - J. Doe

0

我用JavaScript模块成功实现了它,但我怀疑这不是最干净的解决方案。 您可以创建一个名为GlobalStyles.js的文件,其中包含在各种组件中通用的CSS样式。将编辑器的语言模式更改为“html”将为CSS提供语法突出显示。

const GlobalStyles = {
    main: `
        <style>
            body {
                overflow: hidden;
                margin: 0;
                font-family: 'Poppins';
            }
            h3 {
                font-size: 39px;

            }
        </style>
    `,

    button: `
        <style>
            button {
                display: block;
                cursor: pointer;
                outline: none;
                font-family: 'Poppins Medium';
                line-height: 17px;
                padding: 9px 13px;
                font-size: 15px;
                background-color: #9f28d8;
                color: white;
                border: 2px solid;
                border-radius: 5px;
                border-color: #9f28d8;
                width: max-content;
            }
        </style>
    `
}

export default GlobalStyles;

之后,您可以将其导入到另一个包含自定义元素的影子 DOM 代码的 js 文件中。

import GlobalStyles from './GlobalStyles.js';

const template = document.createElement('template');
template.innerHTML = `

   ${GlobalStyles.button}

   <style>
       ul {
           font-family: Helvetica, Arial, sans-serif;
           font-size: 13px;
           width: 20em;
           list-style-type: none;
        }
   </style>



   <ul></ul>

   <button>Click me</button>
`;

export class CustomList extends HTMLElement {
    constructor() {
        super();
        this.attachShadow({ mode: 'open' });
        this.shadowRoot.appendChild(document.importNode(template.content, true));
    }
}

这种方法的缺点是它只适用于纯粹使用js文件的情况。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接