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
4 Answers
Reset to default 2Many 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.