【问题标题】:How to wrap slotted elements individually如何单独包装开槽元素
【发布时间】:2023-12-04 13:30:01
【问题描述】:

有没有办法将每个单独的元素封装到 shadow dom 中以获得特定的插槽名称?

假设标记看起来与此类似

<custom-element>
  <div name="item">item 1</div>
  <div name="item">item 2</div>
</custom-element>

目前,渲染类似于:

<custom-element>
  <div class="wrap">
    <div name="item">item 1</div>
    <div name="item">item 2</div>
  </div>
</custom-element>

我将如何包装开槽元素以输出类似于:

<custom-element>
  <div class="wrap">
    <div name="item">item 1</div>
  </div>
  <div class="wrap">
    <div name="item">item 2</div>
  </div>
</custom-element>

我目前的(有缺陷的)方法:

customElements.define('custom-element', class MyCustomElement extends HTMLElement {

  constructor(...args) {
    super(...args);

    let shadow = this.attachShadow({mode: open});
    shadow.innerHTML = `
      <div class="wrap">
        <slot name="item"></slot>
      </div>
    `;
  }
});

【问题讨论】:

  • 不,您需要编写几行 Javascript 代码来分发元素

标签: javascript html shadow-dom custom-element


【解决方案1】:

我建议您更改包裹孩子的方法。最简单的方法是将缺少的 HTML 添加为开槽 div 的子项,如下例所示。您仍然可以使用 ::slotted 伪选择器设置开槽元素的样式。

customElements.define('custom-element', class MyCustomElement extends HTMLElement {

  constructor(...args) {
    super(...args);

    let shadow = this.attachShadow({ mode: 'open' });
    shadow.innerHTML = `
      <style>
        ::slotted([slot="item"]) {
            border: 1px solid black;
            padding: 15px;
        }
      </style>
      <slot name="item"></slot>
    `;

  }
});
<custom-element>
  <div slot="item">
    <div class="wrap">item 1</div>
  </div>
  <div slot="item">
    <div class="wrap">item 2</div>
  </div>
</custom-element>

这种方法背后的原因是 wrap 最终属于 child 元素并且应该与每个子元素一起出现。结果将与您的要求相似。

不过,如果您确实想为元素动态添加包装,那么您可以使用slotchange 事件。每当一个槽被填满并且可以从ShadowRoot 元素中收听时,就会触发该事件。在assignedElements(插槽中的元素)的事件回调循环中并更改它们的innerHTML值。

customElements.define('custom-element', class MyCustomElement extends HTMLElement {

  constructor(...args) {
    super(...args);

    let shadow = this.attachShadow({mode: 'open'});
    shadow.innerHTML = `
      <slot name="item"></slot>
    `;
    
    shadow.addEventListener('slotchange', event => {
      const { target } = event;
      const assignedElements = target.assignedElements();
      for (const element of assignedElements) {
        element.innerHTML = `
          <div class="wrap">
            ${element.textContent}
          </div>
        `;
      }
    });
    
  }
});
.wrap {
    border: 1px solid black;
    padding: 15px;
}
<custom-element>
  <div slot="item">item 1</div>
  <div slot="item">item 2</div>
</custom-element>

【讨论】:

  • 我一直在寻找 slotchange 事件。