FontAwesome svg within shadow dom
Asked Answered
A

4

5

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

I'm trying to implement an angular component 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.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>
Anacreontic answered 4/5, 2020 at 7:33 Comment(0)
A
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.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>

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>
Affect answered 4/5, 2020 at 9:45 Comment(0)
J
1

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>
Jola answered 31/8, 2022 at 11:36 Comment(0)
S
0

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]
Smokejumper answered 18/9, 2021 at 23:3 Comment(0)
B
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 component 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 component 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/compiled js front-end, checkout the FA documentation for a more straightforward solution for imports.

Bifrost answered 3/12, 2021 at 2:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.