最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - FontAwesome svg within shadow dom - Stack Overflow

programmeradmin0浏览0评论

I'm trying to use the font awsome js/svg library within a web ponent but the icon wont show. is this possible?

I'm trying to implement an angular ponent inside an existing webforms project without css and scripts "bleeding" out, any other suggestions on how to do this? iframe is not an option.

    <html>
    <head>
        <script src=".js"></script>
        <script src=".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=".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>

I'm trying to use the font awsome js/svg library within a web ponent but the icon wont show. is this possible?

I'm trying to implement an angular ponent inside an existing webforms project without css and scripts "bleeding" out, any other suggestions on how to do this? iframe is not an option.

    <html>
    <head>
        <script src="https://polygit/ponents/webponentsjs/webponents-loader.js"></script>
        <script src="https://cdnjs.cloudflare./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./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>
Share Improve this question edited May 4, 2020 at 8:39 user13465186 asked May 4, 2020 at 7:33 user13465186user13465186 511 silver badge2 bronze badges
Add a ment  | 

4 Answers 4

Reset to default 2

Many of those oldskool libraries use document. to access the main DOM.
So they can't do anything with content in shadowDOM

That means your Objective to not bleed scripts out is impossible.
Font-Awesome (script and styles) must be loaded in the main DOM.

If you do not want to bleed styles outside of shadowDOM, you have to play by the rules:

  • Font-Awesome icon definitions have to remain in the main DOM

  • lightDOM is the (main DOM) 'original' for shadowDOM slotted content

  • lightDOM is styled by the main DOM
    (or its shadowDOM container if the element itself is inside another shadowDOM)

  • slotted lightDOM remains in lightDOM , is only reflected to its <slot></slot>

  • You do not want to repeat FontAwesome icon definitions in every lightDOM
    (you might as well not use Custom Elements at all then)

    <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>    
  • A Custom Element can access the whole DOM

Solution:

Write a Custom Element that

  • creates its own lightDOM
  • which is slotted <slot></slot> (reflected! not moved!)
  • takes configuration from attributes
    <awesome-icon background="lightcoral" color="red"></awesome-icon>
    <awesome-icon background="lightgreen" color="green"></awesome-icon>
    <awesome-icon></awesome-icon>

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

<script src="https://cdnjs.cloudflare./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>

NOT using shadowDOM and SLOTs

And rely on scoped CSS properties, makes the code simpler:

<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>

Have you considered using SVG sprites? After a couple of days of looking up alternative solutions to importing the library and figuring out how it would work with the shadow dom, I found that SVG sprites are the easiest solution. The way it is done is with the svg and use method. See font awesome documentation.

I ended up bringing in the sprite file in the dist folder of the project (in this case I brought in bootstrap icons, but any other icon set is the same). dist/icons/bootstrap-icons.svg.

app.js


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

  template.innerHTML = `
  <template>
     <style>
       .icon {
          width: 3rem;
          height: 3rem;
          stroke: currentColor;
          stroke-linecap: round;
          stroke-linejoin: round;
          fill: none;
       }
     </style>
     <svg class="icon">
        <use href="icons/bootstrap-icons.svg#box-arrow-right"/>
     </svg> 
  </template>`;

  customElements.define('my-svg-icon', class extends HTMLElement {
    constructor() {
        super();
        this.attachShadow({ mode: 'open' });
        this.shadowRoot.appendChild(template.content.cloneNode(true));
    }
  });

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Awesome SVG Icons</title>
</head>
<body>
    <my-svg-icon></my-svg-icon>
    <script type="module" src="app.js"></script>
</body>
</html>

I found a way to render SVG font awesome icon in shadow dom.

From the docs, @fortawesome/fontawesome-svg-core package offers more control

here is an example I created using that https://codesandbox.io/s/romantic-panka-40xqm

Basic idea is we can get SVG HTML using icon(faCamera).html[0] and can use this in shadow dom root

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

icon(faCamera).html[0]

Font Awesome has a built in means of parsing/searching for icon tags within a given element to produce SVGs. The following should work for your example, or be very close to what you're looking for:

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

Or if you'd like to get fancy and make it auto hotload any font awesome icons in a web ponent that's a bit more dynamic:

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

FontAwesome is the name of the global variable set by the font-awesome script.

i2svg() without a parameter re-parses the entire light dom (Note, this is unnecessary, since font awesome already watches the dom for changes by default). With a parameter, one is able to pass a js object with a node attribute pointing to some element (light or shadow) and it will generate your SVGs as desired.

For those wanting to do this in a SPA/piled js front-end, checkout the FA documentation for a more straightforward solution for imports.

发布评论

评论列表(0)

  1. 暂无评论