Shadow dom 中的 FontAwesome svg

Posted

技术标签:

【中文标题】Shadow dom 中的 FontAwesome svg【英文标题】:FontAwesome svg within shadow dom 【发布时间】:2020-08-18 13:59:39 【问题描述】:

我正在尝试在 Web 组件中使用字体 awsome js/svg 库,但图标不会显示。这可能吗?

我正在尝试在现有的 webforms 项目中实现一个角度组件,而不会“流出”css 和脚本,还有其他关于如何执行此操作的建议吗? iframe 不是一个选项。

    <html>
    <head>
        <script src="https://polygit.org/components/webcomponentsjs/webcomponents-loader.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.0/js/all.min.js" defer>
        </script>
        <script>
            customElements.define('my-holder', class extends HTMLElement 
                constructor() 
                    super();

                    console.log("constructor");
                    let shadowRoot = this.attachShadow(
                        mode: 'open'
                    );

                    const t = document.querySelector('#holder');
                    const instance = t.content.cloneNode(true);

                    shadowRoot.appendChild(instance);
                

                connectedCallback() 
                    console.log("callback");
                
            );
        </script>
    </head>

    <body>
        <div id="outside">
            light dom
            <div class="fa-4x">
                <span class="fa-layers fa-fw" style="background:MistyRose">
                    <i class="fas fa-circle" style="color:Tomato"></i>
                    <i class="fa-inverse fas fa-times" data-fa-transform="shrink-6"></i>
                </span>
            </div>
        </div>

        <template id="holder">
            <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.0/js/all.min.js" defer></script>
            dark shadow dom
            <div class="fa-4x">
                <span class="fa-layers fa-fw" style="background:MistyRose">
                    <i class="fas fa-circle" style="color:Tomato"></i>
                    <i class="fa-inverse fas fa-times" data-fa-transform="shrink-6"></i>
                </span>
            </div>
        </template>

        <div id="inside">
            <my-holder></my-holder>
        </div>

    </body>
    </html>

【问题讨论】:

【参考方案1】:

许多 oldskool 库使用 document. 访问主 DOM。 所以他们不能对shadowDOM中的内容做任何事情

这意味着您的目标是不流出脚本是不可能的。 Font-Awesome(脚本和样式)必须加载到主 DOM 中。

如果您不想在 shadowDOM 之外使用其他样式,则必须遵守规则:

Font-Awesome 图标定义必须保留在主 DOM 中

lightDOMshadowDOM 插槽内容的(主 DOM)“原始”

lightDOM 由主 DOM 设置样式 (如果元素本身在另一个 shadowDOM 中,则为它的 shadowDOM 容器)

slotted lightDOM 保留在 lightDOM 中,仅反映到它的&lt;slot&gt;&lt;/slot&gt;

您不想在每个 lightDOM 中重复 FontAwesome 图标定义 (那么你最好不使用自定义元素)

    <span class="fa-4x fa-layers fa-fw">
      <i class="fas fa-circle"></i>
      <i class="fa-inverse fas fa-times" data-fa-transform="shrink-6"></i>
    </span>    
自定义元素可以访问整个 DOM

解决办法:

编写一个自定义元素

创建自己的 lightDOM 开槽&lt;slot&gt;&lt;/slot&gt;反映!未移动!) 从属性中获取配置
    <awesome-icon background="lightcoral" color="red"></awesome-icon>
    <awesome-icon background="lightgreen" color="green"></awesome-icon>
    <awesome-icon></awesome-icon>

JSFidlle:https://jsfiddle.net/CustomElementsExamples/1pmvasnj/

<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.0/js/all.min.js" defer></script>
<script>
  customElements.define('awesome-icon', class extends HTMLElement 
    constructor() 
      super().attachShadow(mode: 'open')
      .append(document.getElementById(this.nodeName).content.cloneNode(true));
    
    connectedCallback() 
      let setProperty =
          (prop, value)=>this.shadowRoot.host.style.setProperty('--' + prop, value);
      setProperty('fa-background', this.getAttribute('background'));
      setProperty('fa-color', this.getAttribute('color'));
      // move icon HTML back to lightDOM so FontAwesome can style it
      this.innerHTML = this.shadowRoot.querySelector('#ICON').innerHTML;
    
  );
</script>
<template id="AWESOME-ICON">
  <style>
    ::slotted(*) 
      /* lightDOM SPAN has higher Specificity, only way out is using !important */
      background: var(--fa-background,grey) !important;
      color: var(--fa-color,darkgrey) !important;
    
  </style>
  <template id="ICON">
    <span class="fa-4x fa-layers fa-fw">
      <i class="fas fa-circle"></i>
      <i class="fa-inverse fas fa-times" data-fa-transform="shrink-6"></i>
    </span>    
  </template>
  <slot><!--lightDOM REFLECTED here--></slot>
</template>
<awesome-icon><!-- lightDOM CREATED here --></awesome-icon>
<awesome-icon background="lightcoral" color="red"></awesome-icon>
<awesome-icon background="lightgreen" color="green"></awesome-icon>
<style>
  span
    background:lightblue; /* !important inside shadowDOM overrules these settings */
    color:red;
  
</style>

使用 shadowDOM 和 SLOT

并依靠作用域 CSS 属性,使代码更简单:

<script>
  customElements.define('awesome-icon', class extends HTMLElement 
    connectedCallback() 
      this.append(document.getElementById(this.nodeName).content.cloneNode(true));
      this.style.setProperty('--fa-background', this.getAttribute('background') );
      this.style.setProperty('--fa-color'     , this.getAttribute('color')      );
    
  );
</script>

<template id="AWESOME-ICON">
  <span class="fa-4x fa-layers fa-fw">
     <i class="fas fa-circle"></i>
     <i class="fa-inverse fas fa-times" data-fa-transform="shrink-6"></i>
  </span>
</template>

<awesome-icon background="lightcoral" color="red"></awesome-icon>
<awesome-icon background="lightgreen" color="green"></awesome-icon>
<awesome-icon></awesome-icon>

<style>
  span 
    background: var(--fa-background);
    color:      var(--fa-color     );
  
</style>

【讨论】:

【参考方案2】:

我找到了一种在 shadow dom 中渲染 SVG 字体真棒图标的方法。

从docs,@fortawesome/fontawesome-svg-core 包提供更多控制

这是我使用 https://codesandbox.io/s/romantic-panka-40xqm 创建的示例

基本思想是我们可以使用icon(faCamera).html[0] 获取SVG HTML,并且可以在shadow dom root 中使用它

import  icon  from "@fortawesome/fontawesome-svg-core";
import  faCamera  from "@fortawesome/free-solid-svg-icons";

icon(faCamera).html[0]

【讨论】:

【参考方案3】:

Font Awesome 具有内置的方法,可以解析/搜索给定元素中的图标标签以生成 SVG。以下内容应该适用于您的示例,或者与您要查找的内容非常接近:

FontAwesome.dom.i2svg(
    node: document.querySelector('my-holder').shadowRoot
)

或者,如果您想要花哨并让它自动热加载任何字体很棒的图标到一个更具动态性的 Web 组件中:

connectedCallback() 
  if (this.isConnected) 
    FontAwesome.dom.i2svg(
        node: this.shadowRoot
    )
    FontAwesome.dom.watch(
      autoReplaceSvgRoot: this,
      observeMutationsRoot: this.shadowRoot
    )
    /** Other web component init on connected code here */
  

FontAwesome 是 font-awesome 脚本设置的全局变量的名称。

i2svg() 没有参数会重新解析整个 light dom(注意,这是不必要的,因为 font awesome 已经默认监视 dom 的变化)。使用参数,可以传递一个带有node 属性的js 对象,该属性指向某个元素(光或阴影),它会根据需要生成您的SVG。

对于那些想要在 SPA/编译的 js 前端执行此操作的人,请查看 FA documentation 以获得更直接的导入解决方案。

【讨论】:

以上是关于Shadow dom 中的 FontAwesome svg的主要内容,如果未能解决你的问题,请参考以下文章

shadow-dom浅析

shadow-dom 浅析

神秘的 shadow-dom 浅析

Shadow DOM 是不是像 React.js 中的 Virtual DOM 一样快?

如何从 Selenium 处理 Shadow DOM 中的元素

如何让导入的css字体/图标对shadow dom中的元素产生影响?