5

I have a HTML 5 WebComponent with shadow DOM which displays content that has to load styles depending on the content type that is displayed in the component. The list of stylesheets is fetched from the server.

I can load the stylesheets like this:

for (const style of styles) {
    const stylesheet = document.createElement('link');
    stylesheet.setAttribute('rel', 'stylesheet');
    stylesheet.setAttribute('href', style);
    stylesheet.setAttribute('type', 'text/css');
    this.root.appendChild(stylesheet);
}

However, the stylesheets sometimes also include @font-face rules, which are not added to the component. The browser never creates requests for the fonts references in the @font-face rules. How can I load these rules dynamically?

SebastianR
  • 1,683
  • 2
  • 17
  • 33

2 Answers2

4

It turned out that browsers don't support loading @font-face CSS rules in shadow DOMs as of September 2020. The Chrome team seems to be working on this, though.

The approach I took was to look for @font-face rules in the dynamically created stylesheet, modify them a bit to get relative paths working and then add them to the <head> of the page with a script. If you add the @font-face rules to the head and the shadow DOM, the fonts will be loaded and applied.

Here's the code:

for (const style of styles) { // styles is an array of urls
    const stylesheet = document.createElement('link');
    stylesheet.setAttribute('rel', 'stylesheet');
    stylesheet.setAttribute('href', style);
    stylesheet.setAttribute('type', 'text/css');
    stylesheet.onload = (event) => {
        for (
            let i = 0;
            i < event.currentTarget.sheet.cssRules.length;
            i++
        ) {
            if (event.currentTarget.sheet.cssRules[i].type == 5) { // type 5 is @font-face
                const split = style.split('/');
                const stylePath = split
                    .slice(0, split.length - 1)
                    .join('/');
                let cssText =
                    event.currentTarget.sheet.cssRules[i].cssText;
                cssText = cssText.replace(
                    // relative paths
                    /url\s*\(\s*[\'"]?(?!((\/)|((?:https?:)?\/\/)|(?:data\:?:)))([^\'"\)]+)[\'"]?\s*\)/g,
                    `url("${stylePath}/$4")`
                );

                const st = document.createElement('style');
                st.appendChild(document.createTextNode(cssText));
                document
                    .getElementsByTagName('head')[0]
                    .appendChild(st);
            }
        }
    };
    this.root.appendChild(stylesheet);
}

It works fine on Edge 85 (Chromium based).

SebastianR
  • 1,683
  • 2
  • 17
  • 33
0

You can't do that but you can create a <style> element with JavaScript

Code from #524696

var css = 'h1 { background: red; }',
    head = document.head || document.getElementsByTagName('head')[0],
    style = document.createElement('style');

head.appendChild(style);

style.type = 'text/css';
if (style.styleSheet){
  // This is required for IE8 and below.
  style.styleSheet.cssText = css;
} else {
  style.appendChild(document.createTextNode(css));
}
Majonez.exe
  • 412
  • 8
  • 22
  • I've tried your suggestion and the result is the same: the stylesheets load and display fine, but the fonts added with @font-face are not loaded – SebastianR Sep 02 '20 at 17:42
  • I tested this in codepen and the @font-face fonts is loaded correctly – Majonez.exe Sep 02 '20 at 18:05
  • Turns out that the problem is that the shadow DOM inside the Web Component doesn‘t load the font face. If I turn it off and load the stylesheets globally, the font faces are loaded. Seems to be a limitation of the shadow dom. Whether you load the styles with a style or link tag doesn‘t matter on modern browsers, though. – SebastianR Sep 02 '20 at 19:12
  • You can load fonts in @font-face url in CSS, its better solution than `@import` – Majonez.exe Sep 02 '20 at 19:18
  • I don't use @import – SebastianR Sep 02 '20 at 19:50