Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
255 views
in Technique[技术] by (71.8m points)

::slotted CSS selector for nested children in shadowDOM slot

The CSS ::slotted selector selects children of the <slot> element.

However, when trying to select grandchildren like with ::slotted(*), ::slotted(*) *, or ::slotted(* *), the selector doesn't seem to take effect.

class MyElement extends HTMLElement {
  constructor() {
    super();
    const shadowRoot = this.attachShadow({mode: 'open'})
    shadowRoot.innerHTML = `
      <style>
        ::slotted(*) {
          display: block;
          border: solid blue 1px;
          padding: 3px;
        }
        ::slotted(*) span {
          display: block;
          border: solid red 1px;
          padding: 3px;
        }
        ::slotted(* span) {
          display: block;
          border: solid green 1px;
          padding: 3px;
        }
      </style>
      <slot></slot>
    `;
  }
}
customElements.define('my-element', MyElement);
<my-element>
  <p>
    <span>Test</span>
  </p>
</my-element>
Question&Answers:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

styling ::slotted elements in shadowDOM

TL;DR


background

Yes, ::slotted() not styling nested elements is expected behavior.

The term slotted is counterintuitive,
it implies element lightDOM is moved to shadowDOM

slotted lightDOM is NOT moved, it remains.. hidden.. in lightDOM
the content (IF slotted) is reflected to a <slot></slot>

Or from Google Developer Documentation

????????????????????????, ?????????????????????? ?????????? ?????? ???????? ?? ?????? ??????????????.
?????????? ??????'?? ???????????????????? ???????? ??????; ???????? ???????????? ???? ???? ?????????????? ???????????????? ???????????? ?????? ???????????? ??????.

I use the term reflected instead of render because render implies you can access it in shadowDOM.
You can not, because slotted content isn't in shadowDOM... only reflected from lightDOM.


Why :slotted has limited functionality

More advanced shadowDOM styling was tried.

WebComponents version 0 (v0) had <content> and ::content; but it was removed from the spec:
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/content

The main takeway from the W3C standards discussions
(@hayatoito (Google team) here and here) is:

So in V1 we have :slotted: https://developer.mozilla.org/en-US/docs/Web/CSS/::slotted


Addition #1 : Performance if ::slotted allowed for complex selectors

From Mozilla developer Emilio:

source: https://github.com/w3c/webcomponents/issues/889

The performance issue is that it increments the amount of subtrees in which every node needs to go look for rules that affect to them.

Right now the logic goes like: if you're slotted, traverse your slots and collect rules in their shadow trees as needed. This is the code This is nice because the complexity of styling the element depends directly on the complexity of the shadow trees that you're building, and it only affects slotted nodes.

If you want to allow combinators past slotted then every node would need to look at its ancestor and prev-sibling chain and look at which ones of them are slotted, then do that process for all their slots. Then, on top, you also need to change the general selector-matching code so that selectors that do not contain slotted selectors don't match if you're not in the right shadow tree.

That's a cost that you pay for all elements, regardless of whether you use Shadow DOM or ::slotted, and is probably just not going to fly.


So due to performance issues

:slotted( S ) got limited CSS selector functionality:

  • ? it only takes simple selectors for S. --> Basically anything with a space won't work

  • ? it only targets lightDOM 'skin'. --> In other words, only the first level

<my-element>
  <h1>Hello World</h1> 
  <p class=foo>
    <span>....</span>
  </p>
  <p class=bar>
    <span>....</span>
  </p>
</my-element>
  • ::slotted(h1) and ::slotted(p) works

  • ::slotted(.foo) works

  • ::slotted(span) (or anything deeper) will not work (not a 'skin' element)

Note: ::slotted([Simple Selector]) confirms to Specificity rules,
but (being simple) does not add weight to lightDOM skin selectors, so never gets higher Specificity.
You might need !important in some (rare) use cases.

 <style>
  ::slotted(H1) {
    color: blue !important;
  }
 <style>

Styling slotted content

Also see: Applying more in depth selection to the :host CSS pseudo class

#1 - style lightDOM

The <span> is hidden in lightDOM, any changes made there will continue to reflect to its slotted representation.

That means you can apply any styling you want with CSS in the main DOM
(or a parent shadowDOM container if you wrapped <my-element> in one)

 <style>
  my-element span {
    .. any CSS you want
  }
 <style>

#2 - (workaround) move lightDOM to shadowDOM

If you move lightDOM to shadowDOM with: this.shadowRoot.append(...this.childNodes)

you can do all styling you want in a shadowDOM <style> tag.

Note: You can not use <slot></slot> and :slotted() anymore now.
<slot>s only works with content reflected from lightDOM.

For an example where an element wraps itself in an extra shadowDOM layer,
so no CSS bleeds out, and <slot>s can be used, see:

#3 - ::part (shadow Parts)

It is a different/powerful way of styling shadowDOM content:

Apple finally implemented shadowParts in Safari 13.1, March 2020

see:

Note! ::part styles shadowDOM,
<slot></slot> content remains in lightDOM!


references

be aware: might contain v0 documentation!


Example: Using slots as a router

Change the slot-name on buttonclick and reflect content from lightDOM:

<template id=MY-ELEMENT>
  <style>
    ::slotted([slot="Awesome"]){
      background:lightgreen
    }
  </style>
  <slot><!-- all unslotted content goes here --></slot>
  <slot id=answer name=unanswered></slot>
</template>
<style>/* style all IMGs in lightDOM */
  img { max-height: 165px;border:3px dashed green }
  img:hover{ border-color:red }
</style>
<my-element><!-- content below is: lightDOM! -->
  SLOTs are: &lt

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

1.4m articles

1.4m replys

5 comments

56.9k users

...