简体   繁体   中英

How to externally style parts of a custom element' shadow-DOM with specific CSS classes assigned to them

After reading Monica's article "::part and ::theme, an ::explainer" , I am experimenting with the CSS ::part selector, and the corresponding shadow-DOM elements' part attribute in HTML web components. Now I have some CSS styling issues and I cannot find any specific documentation about it.

To illustrate the issue, I have created a custom element called my-box . Its shadow-DOM just contains a div element with a width and height of 100px. When clicked, the div will get a CSS class marked assigned to it and thus it will be considered to be "marked".

By default, the my-box component will be styled:

  • with a red color palette (lightcoral in normal state, red when hovered) when unmarked, and
  • with a blue color palette (lightblue in normal state, blue when hovered) when marked.

This styling is defined in a style tag in my-box 's shadow-DOM.

Furthermore, to allow external styling of the shadow-DOM's div element, I assign the value "box" to its part attribute. That way, it can be externally referred to using the ::part() CSS selector.

Here's my initial HTML test file's content:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <script type="module">
    customElements.define("my-box", class extends HTMLElement {
      constructor() {
        super();

        const shadowRoot = this.attachShadow({ mode: "open" });

        shadowRoot.innerHTML = `
          <style>
            :host {
              display: block;
            }
            div {
              height: 100px;
              width: 100px;
              background-color: lightcoral;
            }
            div:hover {
              background-color: red;
            }
            div.marked {
              background-color: lightblue;
            }
            div.marked:hover {
              background-color: blue;
            }
          </style>
          <div part="box"></div>
        `;

        const div = shadowRoot.querySelector(":scope div");

        div.addEventListener("click", () => {
          div.classList.add("marked");
        });
      }
    });
  </script>
</head>
<body>
  <my-box>
  </my-box>
</body>
</html>

So far so good. But now I want to externally style the my-box component with the following styling:

  • with a green color palette (lightgreen in normal state, green when hovered) when unmarked, and
  • with a yellow color palette (lightyellow in normal state, yellow when hovered) when marked.

For that purpose, I tried to add the following CSS styling to my test HTML's head:

<style>
  my-box::part(box) {
    background-color: lightgreen;
  }
  my-box::part(box):hover {
    background-color: green;
  }
  my-box::part(box).marked {
    background-color: lightyellow;
  }
  my-box::part(box).marked:hover {
    background-color: yellow;
  }
</style>

But this seems to work only partially. As expected, the green color palette for the unmarked state works correctly. However, the yellow palette for the marked state does not work: the green color palette is also used when the element is marked.

So I guess that the CSS rules for the yellow color palette are invalid. The ::part() selector probably cannot be combined with such class selectors (defined in the shadow-DOM).

Does anybody know how I could get that yellow color palette to work for marked elements? Preferably in a nice and elegant way?

Thanks in advance!

I had an epiphany during supper... ;)

What about simply using the div element's part attribute in combination with the class / classList attribute for "marking" the div element?

So I updated my click eventlistener by adding a statement that sets the div 's part property so that it includes a new part name box-marked . And I updated the external CSS selectors to use the new box-marked part name for the yellow color palette.

So I tried this:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <script>
    customElements.define("my-box", class extends HTMLElement {
      constructor() {
        super();

        const shadowRoot = this.attachShadow({ mode: "open" });

        shadowRoot.innerHTML = `
          <style>
            :host {
              display: block;
            }
            div {
              height: 100px;
              width: 100px;
              background-color: lightcoral;
            }
            div:hover {
              background-color: red;
            }
            div.marked {
              background-color: lightblue;
            }
            div.marked:hover {
              background-color: blue;
            }
          </style>
          <div part="box"></div>
        `;

        const div = shadowRoot.querySelector(":scope div");

        div.addEventListener("click", () => {
          div.classList.add("marked");
          div.part = "box box-marked";
        });
      }
    });
  </script>
  <style>
    my-box::part(box) {
      background-color: lightgreen;
    }
    my-box::part(box):hover {
      background-color: green;
    }
    my-box::part(box-marked) {
      background-color: lightyellow;
    }
    my-box::part(box-marked):hover {
      background-color: yellow;
    }
  </style>
</head>
<body>
  <my-box>
  </my-box>
</body>
</html>

This seems to do the job.

However, I am not sure if the part attribute of HTML elements in the shadow-DOM is (or will be) intended to be used this way. If so, it would be nice if the Element class in JavaScript will also get a comfortable partList property (analogous to its classList property) in the near future. For now, I simply used the part property directly.

Also, the current version of Firefox (68) has issues with handling the example code. The :scope pseudo class has to be removed from the querySelector call's argument to make it find and return the div element. And I also have to use the div element's setAttribute method to change its part attribute, because it does not seem to have implemented the Element.part property just yet. (The Element.part property doesn't seem to be documented on MDN yet either.)

However, even when all that is adjusted, Firefox still does not seem to be capable of parsing the external CSS that uses the ::part() pseudo element for setting the green and yellow color palettes at all; only the internal (shadow-DOM) red and blue color styling is working, even with the external CSS in place.

All the above mentioned issues with Firefox are understandable, of course, since this functionality is still considered to be experimental technology. Regrettably, I have not been able to find an actual browser support overview on Can I Use to validate the current implementation status in Firefox.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM