为什么我在Shadow DOM中不能使用Font Awesome图标?

19

由于我在我的影子DOM中加入了以下内容以防止样式泄露,Font Awesome无法正常工作:

:host {
    all: initial; /* 1st rule so subsequent properties are reset. */
    display: block;
    contain: content; /* Boom. CSS containment FTW. */
}

我可以通过直接将其他样式表内联到:host属性中来使用它们,但是这在Font Awesome中不起作用,因为它在其样式表中使用相对路径。

我发现了这篇文章,并尝试使用我实现的范围CSS,但图标显示为方块,可以在我的示例中看到。


谢谢!这是一种不错的暴力策略,但我会等待看看是否有人可以先帮我链接它。 - cryptojesus
1
你忘记在全局范围内添加<link>了。 - Supersharp
1
@import() 不应该放在规则中,而是直接放在 <style> 元素中(或者被 <link> 替换)。 - Supersharp
1
哇@Supersharp非常感谢! 我真的不明白为什么我需要在ShadowRoot内外链接两次。 你有任何想法为什么吗? - cryptojesus
2
https://codepen.io/anon/pen/NowbXx。是的,FONT(@font-face)必须在全局范围内声明(并在 Shadow DOM 中继承),而类(此处为 fa、fa-search)必须在 Shadow DOM 中导入才能应用于其中。 - Supersharp
显示剩余4条评论
6个回答

22
Stenciljs 的新方法——2023 年更新
如果您正在使用 Stenciljs,并希望直接将字体与捆绑包一起使用,则有两个选项之一适合您:
第一个选项:使用全局样式表https://stenciljs.com/docs/styling#global-styles。在全局样式表中,您可以像通常一样导入字体:
@font-face {
font-family: 'special-font';
src: url('/assets/fonts/special-font.ttf') format('truetype');
}

h1,h2,h3,h4,h5 {
  font-family: 'special-font' !important;
}

这将应用字体到您页面上的所有标题(包括 web 组件和普通页面上的所有元素)。所有内容都通过您的 stencil 包提供。请注意不要意外覆盖您普通网站的样式。

第二个选项:创建一个新组件,如 <ci-styling></ci-styling>,并将所有样式插入到该组件中。然后,您只需在 @Component 装饰器中设置 shadow: false。使用此方法,组件不再使用 shadow dom,样式将在第一个选项中的任何地方都可用。通过这种方法,您可以根据需要在每个页面上决定是否加载字体等内容,只需简单地加载或不加载此组件即可。

@Component({
  tag: 'ci-styles',
  styleUrls: ['ci-styles.scss'],
  shadow: false,
})

以下是原始答案,来自2019年(仍然有效)

第三个选项:经过数小时的挣扎和@Intervalia的回答,我终于解决了问题。

问题在于当字体文件仅包含在影子DOM(您的自定义Web组件)中时,浏览器不会加载它们。这意味着字体也必须包含在正常的HTML文件(即轻量级DOM)中,以便浏览器可以检测并加载它们,以使它们在影子DOM中可用。

在我的情况下,我没有使用Font Awesome,而是使用了自定义字体,但我尝试了第二次使用Font Awesome和一个干净的Stenciljs项目。无论您需要哪种自定义字体,解决方案始终相同。

步骤1:将字体移动到您的项目中。我在“src”文件夹内创建了一个单独的“assets”文件夹,以便所有组件都可以访问。在此示例中,我为Web环境下载了Font Awesome https://fontawesome.com/download。(我不建议使用“npm install”,因为您还必须在index.html中使用它)

enter image description here

步骤2:准备您的Web组件(在本例中为my-component.tsx)。您可以使用styleUrls属性导入多个CSS文件。只需从资产目录导入fontawesome CSS即可。

import { Component, Prop, h } from '@stencil/core';

@Component({
  tag: 'my-component',
  styleUrls: [
    'my-component.css',
    '../../assets/fontawesome/css/all.css'
],
  shadow: true
})
export class MyComponent {
  @Prop() first: string;


  render() {
    return <div> <i class="fas fa-question-circle"></i> </div>;
  }
}

步骤三 准备您想要使用组件的文件(在本例中为index.html)。重要的是"link"标签。这包括再次使用"font awesome css"并强制浏览器真正下载字体。

<!DOCTYPE html>
<html dir="ltr" lang="en">
<head>
  <meta charset="utf-8">
  <title>Stencil Component Starter</title>
  <link rel="stylesheet" type="text/css" href="./assets/fontawesome/css/all.css">
</head>
<body>

  <my-component first="Stencil" last="'Don't call me a framework' JS"></my-component>

</body>
</html>

我知道这感觉很奇怪,看起来也很奇怪,但仅在索引HTML或Web组件中包含字体Awesome是不够的。它必须真正地包含在两个文件中。这并不意味着浏览器会多次加载它 - 它只会��加载一次。

这意味着您不能将字体与Web组件一起提供 - 就我所知。这不是stenciljs的错误,而是浏览器的一般问题。如果您有更好的解决方案,请告诉我。

只是为了好玩,这里有一个截图,显示当它仅包含在一个文件中时,浏览器不会加载字体。 http://prntscr.com/p2f9tc

更新于2019年10月5日:

如果您想在 Web 组件中使用字体,则上面的解释是正确的,仍然是必要的。但是您也可以在 Web 组件内部使用 slot 标签。这样,您就可以自动将字体从外部(HTML)绕过并传递到 Web 组件中。但请注意,它仅适用于您在 Web 组件标签之间编写的内容。

这意味着您可以使用 <my-component> <i class="your-font"/> </my-component>。在这种情况下,您不必将字体导入到 Web 组件中。


我也遇到了Stenciljs的同样问题。我已经阅读了你的答案,但我仍然不清楚如何在stencil中使用Font Awesome。 我运行了npm i font-awesome,接下来我需要做什么呢? 我的项目中没有asset文件夹,而你所写的formate.css是什么? - JEricaM
1
在我的示例中,我使用了不同的字体(而不是 font awesome)。但问题始终如此。为了验证解决方案,我刚刚尝试了一下 font awesome 和一个新的 stencil 项目,它可以正常工作。据我所见,问题在于浏览器只有在 Shadow Dom 中嵌入字体时才不会加载字体。我将更新我的答案,提供更多细节和更普遍的方式。 - Christian Meyer
1
谢谢你,Christian。我已经按照你的教程在一个新的Stencil组件项目中进行了跟随,并且一切都完美运行!非常感谢你的帮助。现在我将尝试将其集成到我的另一个使用storybook的stencil项目中。 - JEricaM
感谢您提供的解决方案,它仅适用于模板组件。当我尝试使用已经构建的组件(在模板中工作),但在ReactJS应用程序中不显示图标时。您是否尝试过在ReactJS中使用构建的组件? :) - Johnson Samuel
我更倾向于Angular,抱歉;) 我已经在最新的Angular和AngularJS中成功地使用了上面解释的组件,所以我相信它也可以在ReactJS中工作。过程始终是相同的。我上面提到的index.html仅适用于Stencil.js,并且不会以任何形式呈现为Web组件。index.html仅适用于StencilJS中集成的Web服务器(localhost:3333)。您必须确保直接在ReactJS中导入字体和CSS。我猜想ReactJS也使用index.html作为起点-尝试直接在那里导入css文件。 - Christian Meyer
我正在使用line-awesome,这对我很有帮助。基本上是相同的两个步骤。
  1. 在index.html中包含css - 作为链接标签
  2. 在组件的.css中作为@import url包含css,或者在styles数组中包含。
- Roshan Khandelwal

8

我注意到的一个问题是,如果页面没有加载CSS文件,则shadowDOM也不会加载它。

我认为唯一的问题是,如果页面上没有定义字体,那么组件中也无法工作,因为其他CSS似乎已正确应用于shadowDOM元素。

这个例子只展示了shadowDOM试图加载CSS,但失败了:

let template = `
<style>
:host {
  display: block;
}
</style>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.1/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
<header>
  <h1>DreamLine</h1>
  <nav>
    <ul>
      <li><a href="#0">Tour</a></li>
      <li><a href="#0">Blog</a></li>
      <li><a href="#0">Contact</a></li>
      <li><a href="#0">Error</a></li>
      <li><a href="#0"><i class="fa fa-search"></i> Search</a></li>
    </ul>
  </nav>
</header>
`;

class MyEl extends HTMLElement {
  connectedCallback() {
    this.attachShadow({mode: 'open'}).innerHTML = template;
  }
}

customElements.define("blog-header", MyEl);
<i class="fa fa-battery-full" style="font-size: 45px;"></i>
<hr/>
<blog-header></blog-header>
<hr/>

这个示例展示了页面和 shadowDOM 都被加载,并且它可以正常工作:

let template = `
<style>
:host {
  display: block;
}
</style>
<header>
  <h1>DreamLine</h1>
  <nav>
    <ul>
      <li><a href="#0">Tour</a></li>
      <li><a href="#0">Blog</a></li>
      <li><a href="#0">Contact</a></li>
      <li><a href="#0">Error</a></li>
      <li><a href="#0"><i class="fa fa-search"></i> Search</a></li>
    </ul>
  </nav>
</header>
`;

class MyEl extends HTMLElement {
  connectedCallback() {
    const styles = document.querySelector('link[href*="fontawesome"]');
    this.attachShadow({mode: 'open'}).innerHTML = template;
    if (styles) {
      this.shadowRoot.appendChild(styles.cloneNode());
    }
  }
}

customElements.define("blog-header", MyEl);
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.1/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">

<i class="fa fa-battery-full" style="font-size: 45px;"></i>
<hr/>
<blog-header></blog-header>
<hr/>

我喜欢使用的代码会在主体中寻找我想要的<link>标签,然后使用该标签的克隆版本放置在shadowDOM中。这样我的组件就不会失步了。如果组件没有预料到CSS的更改,那么是的,这可能会引起问题,但我发现它对我的项目很有效。

这可能已经在此期间发生了变化,但现在我只需要在主机页面中包含 all.min.css 就可以了。其他解决方案提到尝试使用插槽,这也有点奏效,但我想要在模板中具有相同名称的多个实例,这似乎不起作用(只有第一个接受给定名称的所有输入)。我在 FF 和 Chrome 上进行了测试,并具有相同的行为 - 除了将 FA css 放在主机页面中外,没有任何作用。(是的,此组件正在使用影子 DOM) - AntonOfTheWoods

1
我想分享一下我是如何在我的阴影组件中加载Font Awesome图标的...
在对这个主题进行数小时的研究之后,我认为我已经发现了一个最有效的解决方案,可以使我的组件以不可知的方式捆绑,并且不需要在HTML头部包含任何其他样式表。
我的解决方案是使用stencil-inline-svg模块,然后直接从Font Awesome模块导入svg文件,就像这样:
// the reference to the svg can be anything (searchIcon name).
// just make sure to import the correct svg from fontawesome node modules.
import searchIcon from 'fontawesome/svgs/regular/search.svg';

@Component({
  tag: 'search-icon',
  styleUrl: 'search-icon.scss',
  shadow: true,
})

export class SearchIconComponent {
  render(){
    return (
      {/* Not ideal to use innerHTML but this renders the full SVG markup */}
      <span class="search-btn" innerHTML={searchIcon}></span>
    )
  }
}

现在,我可以设置CSS规则来修改图标的颜色和大小,就像这样。
    .search-btn {
        width: 40px; // Set SVG size at the parent.

        svg path {
            fill: #fff; // Update svg path color.
        }
    }

显然这需要一些 Font Awesome 图标知识,以便您知道要导入哪些图标。

1
优秀的解决方案!我喜欢它,因为它不会污染外部网页的样式。对于那些感兴趣的人,这个页面展示了如何在React中使用这种方法:https://fontawesome.com/how-to-use/on-the-web/using-with/react。 - Christian Fritz

1
如果您不需要使用 shadow: true,则可以通过 index.html 或主应用程序直接加载 all.min.css。甚至加载 all.min.js 文件也可以。
如果您需要在 shadow dom 中使用它,则需要在 index.html 中加载 all.min.css,并在 shadow root 中使用类似以下的方式进行加载。

`

componentDidLoad(): void {
  this.hostElement.shadowRoot
    .getElementById("some_Id")
    .insertAdjacentHTML(
      "afterbegin",
      `<link rel="stylesheet" href="${getAssetPath(
        "/fontAssets/fontawesome-free/css/all.min.css"
      )}" />`
    );
}

`


0

Shadow Dom 的样式是有作用域的。它不会干扰外部样式。


0

顺便说一下,我创建了一个帮助方法,在父页面级别上创建一个 font-awesome 链接。不确定这是否违反了任何自定义元素/Web 组件标准,但我会继续发布在这里,希望能得到纠正 :) 目前它适用于我的内部 Web 应用程序使用案例。

export const addFontAwesomeStyles = () => {
    injectStylesheet("https://use.fontawesome.com/releases/v5.13.0/css/all.css");
    injectStylesheet("https://use.fontawesome.com/releases/v5.13.0/css/v4-shims.css");
}

export const injectStylesheet = (href: string) => {
    const links = document.head.querySelectorAll("link");

    // Already been injected
    for(let l in links)
        if(links[l].href == href) return;

    const link = document.createElement('link');
    link.rel = "stylesheet";
    link.href = href;
    document.head.appendChild(link)
}

然后在您的StencilJS组件的构造函数中,您可以像这样使用它:

//...
constructor() {
    addFontAwesomeStyles();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接