【问题标题】:Style nested web component where child can be same type as, but needs different styles to, parent样式嵌套的 Web 组件,其中子级可以与父级具有相同的类型,但需要不同的样式
【发布时间】:2021-07-20 15:16:07
【问题描述】:

我开发了一个 HTML Web 组件,其插槽使用 HTML theme 属性来启用子元素的间距(通过对它们应用 margin)。我想使用 CSS 自定义属性 --spacing-size 来控制间距的大小,该属性设置在每个组件实例的 HTML style 属性上。

class VerticalLayout extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: "open"});
  }

  connectedCallback() {
    this.shadowRoot.innerHTML = 
      `<style>
         :host {
           display: flex;
           flex-direction: column;
         }

         :host([theme~="spacing"]) ::slotted(:not(:first-child)) {
           margin-top: var(--spacing-size);
         }
       </style>
       <slot></slot>`;
  }
}

customElements.define("vertical-layout", VerticalLayout);

当我将我的组件的一个实例添加到另一个实例的槽中时遇到了问题,因为我的 CSS 以槽元素为目标来为它们提供边距。由于在这种情况下,开槽元素 我的组件,其值为 --spacing-size 表示 子级,因此它获得了为其子级的边距而不是所需的边距在父级内部。

<vertical-layout theme="spacing" style="--spacing-size: 2em;">
  <div>No margin on this, it is the first child</div>
  <!-- I want a 2em margin on the next element (the size  -->
  <!-- from the layout it sits within), but it gets 0.2em -->
  <!-- because --spacing-size has been redefined -->
  <vertical-layout theme="spacing" style="--spacing-size: 0.2em;">
    <div>No margin on this, it is the first child</div>
    <div>0.2em margin above this</div>
  </vertical-layout>
</vertical-layout>

我创建了一个codepen。请参阅 codepen,我通过添加第二个自定义属性 --parent-size 克服了这个问题。 codepen 说明了我期望的布局间距,但是有没有一种聪明的方法可以只使用一个自定义属性来实现相同的行为?

在 codepen 中看到,另一个复杂之处是我明确地将 --spacing-size 设置为 CSS 中的默认值,以便在打开主题但未指定大小时应用。我想这可能会使继承父级中的任何值变得非常困难......

我觉得:host-context() 选择器可能是答案,但我不能完全掌握如何使用它(而且,由于 Safari 不支持它,我无论如何都必须寻找另一种解决方案)。

【问题讨论】:

  • 如果我可以结合 :host:has::slotted 选择器来设置任何内部有另一个插槽的组件的父大小,就像这样:@987654335 @唉,这行不通。
  • 我试过了,但它太多了,无法理解你想要什么。你能做更小的sn-ps吗?并使用编辑器中的 [ ] 按钮添加 SO sn-ps 而不是 CodePen。
  • @Danny'365CSI'Engelman,感谢收看,我做了一些编辑,是否更清楚?我真的在想我不能用一个自定义属性来做到这一点,除非......也许有一种方法可以包装添加到插槽中的元素?我还想知道在 connectedCallback 中运行的 JS 是否有可能查看任何父实例(因此越过影子根)并生成我手动添加到我的 codepen 中的 --parent-size 变量?
  • 也许您可以用div 包裹嵌套的vertical-layout? div 将获取父级的边距。
  • @Auroratide 我有同样的想法,有没有一种巧妙的方法可以从 Web 组件中做到这一点?我尝试从后端执行此操作,但遇到的一个问题是,在实践中,组件应用了样式(例如宽度:100%),所以我的布局看起来很糟糕——我想我需要一些方法来复制样式也在包装上。

标签: css css-selectors web-component css-variables


【解决方案1】:

花了一些时间来完全理解你想要什么(我可能是错的)

  • 您想为所有儿童(第一个儿童除外)指定margin-top
    与:&lt;vertical-layout childmargin="2em"&gt;
  • 对于嵌套 &lt;vertical-layout&gt; 元素应该有margin-top 其父容器

您的问题:&lt;vertical-layout style="--spacing-size: 2em"&gt;2em 设置在 &lt;vertical-layout&gt; 本身(及其所有子级)

您希望它应用于仅限儿童

shadowDOM 中的 CSS 无法做到这一点;因为这不会设置 slotted 内容的样式。
见:::slotted CSS selector for nested children in shadowDOM slot


我已更改您的 HTML 和属性以反映您想要的边距:

(更好理解的 px 符号)

    0px <vertical-layout id="Level1" childmargin="15px">
    15px  <div>child1-1</div>
    15px  <div>child1-2</div>
    15px  <div>child1-3</div>
    15px  <vertical-layout id="Level2" childmargin="10px">
    0px    <div>child2-1</div>
    10px   <div>child2-2</div>
    10px   <vertical-layout id="Level3" childmargin="5px">
    5px      <div>child3-1</div>
    5px      <div>child3-2</div>
    5px      <div>child3-3</div>
           </vertical-layout>
    10px    <div>child2-3</div>
         </vertical-layout>
    15px <div>child1-4</div>
    15px <div>child1-5</div>
        </vertical-layout>

CSS 无法读取childmargin ;所以需要 JS 将该值应用于 子元素

因为您也不想设置 first-child...

connectedCallback 的代码是:

    connectedCallback() {
      let margin = this.getAttribute("childmargin");
      setTimeout(() => {
        let children = [...this.querySelectorAll("*:not(:first-child)")];
        children.forEach(child=>child.style.setProperty("--childmargin", margin));
      });
    }

注意事项

  • * 有点残酷。如果您有大量子元素,您可能需要使用更具体的选择器;也许:
[...this.children].forEach((child,idx)=>{
  if(idx) ....
};
  • 你正在循环所有的孩子;也可以直接在这里设置样式..然后不需要CSS

  • setTimeout 是必需的,因为当connectedCallback 触发时,所有子尚未被解析

因为您所有的 &lt;vertical-layout&gt; 都在 GLOBAL DOM 中(并让 reflected&lt;slot&gt; 元素)

您在 GLOBAL CSS 中为所有内容设置样式:

  vertical-layout > *:not(:first-child)  {
    margin-top: var(--childmargin);
  }

那么所需的所有 Web 组件代码是:

customElements.define("vertical-layout", class extends HTMLElement {
  constructor() {
    super()
     .attachShadow({mode:"open"})
     .innerHTML = "<style>:host{display:flex;flex-direction:column}</style><slot></slot>";
  }
  connectedCallback() {
    let margin = this.getAttribute("childmargin");
    setTimeout(() => {
        let children = [...this.querySelectorAll("*:not(:first-child)")];
        children.forEach(child=>child.style.setProperty("--childmargin", margin));
      });
  }
});

或者在一个完整的游乐场:

https://jsfiddle.net/CustomElementsExamples/sywr8v4f/

<vertical-layout id="Level1" childmargin="15px">
  <div>child1-1</div>
  <div>child1-2</div>
  <div>child1-3</div>
  <vertical-layout id="Level2" childmargin="10px">
    <div>child2-1</div>
    <div>child2-2</div>
    <vertical-layout id="Level3" childmargin="5px">
      <div>child3-1</div>
      <div>child3-2</div>
      <div>child3-3</div>
    </vertical-layout>
    <div>child2-3</div>
  </vertical-layout>
  <div>child1-4</div>
  <div>child1-5</div>
</vertical-layout>

<style>
  body {
    font: 12px arial;
  }
  vertical-layout > *:not(:first-child)  {
    font-weight: bold;
    margin-top: var(--childmargin);
  }
  vertical-layout::before {
    content: "<vertical-layout " attr(id) " childmargin=" attr(childmargin);
  }
  vertical-layout > vertical-layout {
    background: lightblue;
    border-top: 4px dashed red;
  }
  vertical-layout > vertical-layout > vertical-layout {
    background: lightcoral;
  }
</style>

<script>
  customElements.define("vertical-layout", class extends HTMLElement {
    constructor() {
      super()
        .attachShadow({
          mode: "open"
        })
        .innerHTML =
        `<style>
         :host {
           display: flex;
           flex-direction: column;
           background:lightgreen;
           padding-left:20px;
           border:2px solid red;
         }
         ::slotted(*){margin-left:20px}
         :host([childmargin]) ::slotted(:not(:first-child)) {
           color:blue;
         }
       </style>
       &lt;slot>
       <slot></slot>
       &lt;/slot>`;
    }
    connectedCallback() {
      let margin = this.getAttribute("childmargin");
      setTimeout(() => {
        let children = [...this.querySelectorAll("*:not(:first-child)")];
        children.map(child=>{
          child.style.setProperty("--childmargin", margin);
          child.append(` margin-top: ${margin}`);
        })
      });
    }
  });

</script>

【讨论】:

  • 非常感谢您抽出宝贵的时间来做这件事,很抱歉我无法让问题更容易理解。你在这里教会了我很多东西,你的努力非常感激。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-12-20
  • 2020-08-14
  • 1970-01-01
  • 1970-01-01
  • 2023-03-28
  • 1970-01-01
  • 2014-11-15
相关资源
最近更新 更多