CSS 无法在自定义 HTML 元素上正常工作

CSS not working properly on Custom HTML Elements

我一直在尝试通过扩展 HTMLElement class 来制作自定义 HTML 元素。我尝试通过链接与我的其他两个文件位于同一目录中的 CSS 文件来为其添加一些样式 - index.htmlcustom.css.

主文件夹

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>Document</title>
    <link rel="nofollow" type="text/css" href=''>
</head>
 
    <body>
        <script src="./custom.js"></script>
        <smooth-button text="Smooth button" no-1 = 1 no-2 = 2></smooth-button>
    </body>
 
</html>

custom.css:

smooth-button{
    display: block;
    color: blue;
    background-color: orange;
}

custom.js:

class SmoothButton extends HTMLElement{
 
    constructor(){
        super();
        this.shadow = this.attachShadow({mode: "open"})
    }
 
    connectedCallback(){
        this.render();
    }
 
    render(){
        this.SumOfNo1AndNo2 = null;
        if(this.getAttribute("no-1")!=null && this.getAttribute("no-2")!=null){
            this.SumOfNo1AndNo2 = parseInt(this.getAttribute("no-1")) + 
            parseInt(this.getAttribute("no-2"));
        }
        else{
            console.log("Invalid attribute.")
        }
        this.shadow.innerHTML = `<button>` + this.getAttribute("text") + " " + this.SumOfNo1AndNo2   
        + "</button>"
    }
 
}
 
customElements.define("smooth-button", SmoothButton);  

有了这个,我按预期得到了一个带有文本的按钮,但样式应用于整个元素,而不是应用于组成它的元素。如何使用外部 CSS 文件将样式分别应用于其每个元素(目前只是一个 <button>)?我正在使用外部 CSS 因为它在我阅读时更好 .

With this, I get a button as expected, with the text, but the style is applied to the element as a whole and not to the elements it's made of.

这实际上是自定义元素的工作方式。您不能将样式应用于外部文档中的阴影 DOM。如果可以,您很可能会通过外部修改破坏自定义元素样式。

然而一切并没有丢失!按钮与背景颜色不同的原因是用户代理样式表。你实际上可以设置一些CSS来告诉背景继承父背景颜色。尝试将此添加到您的自定义元素中:

const style = document.createElement('style');
style.textContent = `
  button {
    background: inherit;
  }
`;
this.shadow.append(style);

JSFiddle:https://jsfiddle.net/5t2m3bku/

(另请注意,将 interpolate/concatenate 文本直接输入 HTML 并不是一个好主意。该文本会被解释为 HTML,这可能导致无效的 HTML 如果使用保留字符,甚至潜在的 XSS 漏洞。您可以修改设置 innerHTML 的那一行来设置文本,或者切换到模板引擎。)

除了布拉德的回答。将样式从光 DOM 应用到阴影 DOM 的方法之一是使用 CSS 变量。

smooth-button{
  display: block;

  --button-color: blue;
  --button-background-color: orange;
}
render() {
  this.shadow.innerHTML = `
    <style>
      button {
        color: var(--button-color);
        background-color: var(--button-background-color);
      }
    </style>

    <button>
      ${this.getAttribute("text")} ${this.SumOfNo1AndNo2}   
    </button>
  `;
)

除了 Brad 和 Emiel 的回答,

  • (Brad) 直接在 shadowDOM 里面加一个 <style> 元素
  • (Emiel) 使用级联 CSS 属性
  • 有更多选项可以设置 shadowDOM 的样式:

了解可继承样式

使用阴影部分

<style>
  ::part(smoothButton){
    display: block;
    color: blue;
    background-color: orange;
  }
</style>

<smooth-button></smooth-button>
<smooth-button></smooth-button>

<script>
  customElements.define("smooth-button", class extends HTMLElement {
    constructor(){
      super()
        .attachShadow({mode:"open"})
        .innerHTML = `<button part="smoothButton">LABEL</button>`;
    }
  });
</script>

但是...

你应该问自己的第一个问题:

Do I really need shadowDOM?

如果你不想要它的封装行为,那么不要使用shadowDOM

<style>
  .smoothButton{
    display: block;
    color: blue;
    background-color: orange;
  }
</style>

<smooth-button></smooth-button>
<smooth-button></smooth-button>

<script>
  customElements.define("smooth-button", class extends HTMLElement {
    connectedCallback(){
      this.innerHTML = `<button class="smoothButton">LABEL</button>`;
    }
  });
</script>

shadowDOM <slot>

另一种选择是使用 shadowDOM <slot> 元素,因为它们由其容器元素设置样式

<style>
  .smoothButton{
    display: block;
    color: blue;
    background-color: orange;
  }
</style>

<smooth-button><button class="smoothButton">LABEL</button></smooth-button>
<smooth-button><button class="smoothButton">LABEL</button></smooth-button>

<script>
  customElements.define("smooth-button", class extends HTMLElement {
    constructor(){
      super()
        .attachShadow({mode:"open"})
        .innerHTML = `<slot></slot>`;
    }
  });
</script>

当你进入 <slot> 兔子洞时,一定要阅读(很长)post: