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

Posted

技术标签:

【中文标题】将全局样式应用到 Shadow DOM 的正确方法【英文标题】:Correct way to apply global styles into Shadow DOM 【发布时间】:2019-04-14 03:08:47 【问题描述】:

这个问题与 *** 上的其他问题类似,但我找不到任何描述适用于我的情况和非弃用方法的答案(我开始认为可能没有任何好的解决方案来解决这种情况) .

假设我们有一些 main.css 文件,其中包含按钮、列表、链接等的常用样式。所以它只是一些标准的 .css 文件,其中包含我们希望在应用程序中重用的常见样式。我们希望将相同的样式应用于带有 Shadow DOM 的 Web 组件。

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

    使用已弃用的方法之一:::shadow、>>>、/deep/ 选择器。但是这些选择器现在已经被弃用了,所以我想这不是前进的好方法。 使用 css 变量。如果我们需要设置一些属性,这种方法非常适合自定义目的。但是如果我们想从 main.css 文件中迁移 10-20 种常用样式,那就太复杂了。 在 Shadow DOM 中使用 @import 语句或“链接”标签。它会起作用,但它会复制每个组件的所有样式。如果我们有 10 个 Web 组件,我们最终会得到 10 个完全相同样式的副本。这听起来也不是足够好的解决方案。尤其是如果我们有很多常见的样式,从性能的角度来看,这听起来可能是一个糟糕的解决方案。 根本不要使用 Shadow DOM 并使用全局样式 :) 但这不是当前问题的解决方案。

我还检查了在 Angular 框架中如何解决相同的问题(我检查了 Angular 的第 5 版)。当我将封装行为设置为 Native 时,它​​实际上只是在复制样式(如上述 #3 中所述),我认为这不是最好的方式(但可能是目前最好的方式)。

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

【问题讨论】:

【参考方案1】:

解决方案 3 没有真正的缺点:

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

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

在那之后,il 将依赖于 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.adpotedStyleSheets = [css]

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

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

【讨论】:

FWIW,a polyfill for the features is available. 似乎 @import for replaceSync 现在被忽略了 github.com/WICG/construct-stylesheets/issues/… 关于解决方案 3 没有缺点。当我使用 global.css 文件时,会为每个使用它的组件获取它。因此,当我附加一个自定义元素时,我会出现很大的闪烁,其中包含深度嵌套的自定义元素 @J.Doe 所以你认为这是解决方案 3 的缺点吗?抱歉,我没有看到你的评论。 @Supersharp 当我在多个 shadow dom 中导入同一个 css 文件时,每次都会从服务器获取 css 文件。因此,每次我向 dom 添加包含此 css 文件的自定义元素时,它都没有样式,直到获取 css 文件,这会导致无样式 html 的“闪烁”。当我嵌套了 Web 组件时,这个问题会变得更糟【参考方案2】:

我设法使用 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;

之后,您可以将其导入另一个 js 文件,该文件包含自定义元素的 shadow dom 的代码。

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 文件时才有效。

【讨论】:

以上是关于将全局样式应用到 Shadow DOM 的正确方法的主要内容,如果未能解决你的问题,请参考以下文章

聚合:将样式应用于Shadow DOM下的元素

JS 动态修改CSS 样式方法/全局

为原生 Shadow DOM 元素设置样式

shadow dom

通过 Webpack 将 CSS 样式注入到 shadow root

react项目中对dom元素样式修改的另一种方法以及将组件插入到node节点中