阴影DOM和浅色DOM应该包含哪些部分?

时间:2019-01-22 02:30:36

标签: javascript dom web-component shadow-dom custom-element

我正在学习Web组件。在设计自定义元素时,我必须决定在影子DOM中将隐藏什么。然后,其余部分将在轻型DOM中曝光。

据我了解,API允许两个极端的使用案例,但需要权衡取舍:

  • 在影子DOM中几乎不隐藏任何内容,大多数元素的内容都在轻DOM和元素的属性中:
    • 这允许HTML作者在不编写JS的情况下提供任何要显示的组件;
    • 这接近于可搜索性和可访问性的现状
    • 但是所涉及的工作没有什么回报;我增加了组件的复杂性,但它们没有封装任何东西(所有东西都暴露了)。
  • 隐藏阴影DOM中的几乎所有内容,该元素的innerHTML为空:
    • 这需要从JS实例化元素;
    • 这更多地锁定了用法,因为从JS实例化(在类型上)比使用HTML插槽和属性更严格;
    • 这可能不太容易搜索和访问(我不确定是否是这种情况);

由于以下原因,我目前倾向于将所有内容隐藏在影子DOM中:

  • 我打算实例化JS中的所有内容。我不会手动编写HTML页面。同时对HTML API和JS API进行编码将需要更多的工作。
  • 隐藏所有内容要少花些精力。我不需要在轻型DOM中可见的信息之间找到适当的平衡。
  • 它更接近于我所熟悉的大多数JS框架。

我想念什么吗?


编辑

谢谢,我得到答复,这取决于部分回答了我的问题的用例。但是我仍然缺少关于所处情况的答案:我宁愿不为某些组件提供插槽。

我将为光谱的每个极端添加一个示例:

  • 轻量级DOM组件:该组件用户必须将元素插入插槽

    <template id=light-email-view>
      <div>
        <div><slot name=from></slot></div>
        <ul><slot name=to></slot></ul>
        <h1><slot name=subject></slot></h1>
        <div><slot name=content></slot></div>
        <ul><slot name=attachements></slot></ul>
        <div class=zero-attachment-fallback>no attachments</div>
      </div>
    </template>
    

  • 阴影DOM重组件:该组件用户必须使用JS API。

    <template id=shadow-email-view>
      <div></div>
    </template>
    <script>
    ...
    let view = document.createElement('shadow-email-view');
    // this method renders the email in the shadow DOM entirely
    view.renderFromOject(email);
    container.appendChild(view);
    </script>
    

    在第一个示例中,组件作者需要做更多的工作,因为他们需要“解析” DOM:他们必须计算附件来切换回退。基本上,不是浏览器将元素从浅DOM复制到匹配的阴影DOM插槽中的任何输入转换。然后,他们需要侦听属性更改等信息。组件用户还需要做更多的工作,他们必须将正确的元素插入正确的插槽中,其中一些是不重要的(电子邮件内容可能必须链接)。

    在第二个示例中,组件作者不需要实现对从带有槽的HTML实例化的支持。但是组件用户必须从JS实例化。 全部:渲染由组件作者以.renderFromObject方法完成。如果需要,一些其他方法提供了挂钩来更新视图。

    可以通过让组件同时提供插槽和JS帮助程序来主张中间立场。但是我看不出HTML编写者不使用该组件的意义,还有很多工作要做。

    因此,将所有可用的影子DOM置于可行状态还是我提供插槽,因为不这样做不符合标准,并且我的代码将破坏某些期望它们的用户代理(忽略旧版本)完全不了解自定义元素的UA)?

  • 4 个答案:

    答案 0 :(得分:5)

    根据用例的选择是100%。

    也:

    • 如果希望用户能够使用全局CSS样式属性设置自定义元素的格式,则可以选择普通的轻型DOM。

    • 您是对的:在Shadow DOM中,“这可能不太容易搜索”:document.querySelector()方法不会检查Shadow DOM的内容。

    • 因此,
    • 一些third-pary JS library may fail可以轻松地与Shadow DOM集成

    • 如果您打算在旧版浏览器中使用“自定义元素”填充,请避免使用Shadow DOM,因为某些功能无法真正进行填充。

    • 在许多情况下,答案是混合使用Light DOM和Shadow DOM。如@JaredSmith所建议:

      • Web组件作者的影子DOM
      • Web Compoent用户的Light DOM,在Shadow DOM中与<slot>集成在一起。

    作为结论,您应该考虑将Web组件用于确定是否需要Shadow DOM的上下文。


    回答修改

    考虑到您的用例,我将创建一个自定义元素,然后:

    • 让用户使用原子值填充轻型DOM:输入元素<div class="mail-to">或@Intervalia建议的自定义子组件<mail-to>
    • 使用Shadow DOM遮盖浅色DOM,
    • 使用Javascript:this.querySelectorAll('.mail-to')this.querySelectorAll('mail-to')而不是<slot>从轻型DOM提取数据并将其复制(或移动到)影子DOM。

    通过这种方式,用户将不必学习<slot>的工作原理,并且开发人员将能够更加自由地设置Web组件渲染的格式。

    <email-view>
      <mail-to>guillaume@stackoverflow.com</mail-to>
      <mail-to>joe@google.fr</mail-to>
      <mail-from>supersharp@cyber-nation.fr</mail-from>
      <mail-body>hello world!</mail-body>
    <email-view>
    

    答案 1 :(得分:4)

    @supersharp已将其钉住。

    我对Web组件看到的一件事是,人们倾向于使他们的组件做得太多而不是分解成较小的组件。

    让我们考虑一些本机元素:

    <form>没有影子DOM,它唯一要做的就是从其子表单元素中读取值以能够进行HTTP GET,POST等。

    <video> 100%shadowDOM,它使用应用程序提供的子级对象的唯一作用是定义要播放的视频。用户无法为<video>标签的阴影子元素调整 any CSS。也不应允许他们这样做。 <video>标签唯一允许的是隐藏或显示那些影子孩子的能力。 <audio>标签执行相同的操作。

    <h1><h6>无阴影。这一切都是设置默认字体大小并显示子级。

    <img>标签使用阴影子元素来显示图像和Alt-Text。

    就像@supersharp所说的,shadowDOM的使用是基于元素的。我还要进一步说,shadowDOM应该是一个深思熟虑的选择。我要补充一点,您需要记住,这些应该是组件而不是应用程序。

    是的,您可以将整个应用程序封装到一个组件中,但是浏览器并未尝试使用Native组件来做到这一点。您可以使组件变得更加专业化,使其更可重用。

    避免在您的Web组件中添加不是普通JS的任何内容,换句话说,请勿在您的组件中添加任何框架代码,除非您永远不想与不使用它们的人共享它们框架。我写的组件是100%Vanilla JS,没有CSS框架。并且它们在Angular,React和vue中使用,对代码进行 no 更改。

    但是选择为每个编写的组件使用shadowDOM。而且,如果您必须在本机不支持Web组件的浏览器中工作,则可能根本不想使用shadowDOM。

    最后一件事。如果编写的组件不使用shadowDOM但具有CSS,则必须小心放置CSS,因为您的组件可能会放置在其他人的shadowDOM中。如果将CSS放在<head>标记中,则它将在另一个shadowDOM中失败。我使用此代码来防止该问题:

    function setCss(el, styleEl) {
      let comp = (styleEl instanceof DocumentFragment ? styleEl.querySelector('style') : styleEl).getAttribute('component');
      if (!comp) {
        throw new Error('Your `<style>` tag must set the attribute `component` to the component name. (Like: `<style component="my-element">`)');
      }
    
      let doc = document.head; // If shadow DOM isn't supported place the CSS in `<head>`
      // istanbul ignore else
      if (el.getRootNode) {
        doc = el.getRootNode();
        // istanbul ignore else
        if (doc === document) {
          doc = document.head;
        }
      }
    
      // istanbul ignore else
      if (!doc.querySelector(`style[component="${comp}"]`)) {
        doc.appendChild(styleEl.cloneNode(true));
      }
    }
    
    export default setCss;
    

    答案 2 :(得分:3)

    好的。抛开一会儿,我认为这是一个 bad 可疑的想法,下面是一些代码,它可以满足您的要求(我没有运行它,但是它应该可以工作):

    class FooElement extends HTMLElement {
      constructor () {
        super();
        this.attachShadow({ mode: 'open' });
        this.shadowRoot.appendChild(document.importNode(template.content, true));
      }
    
      _xformObject (object) {
        // turn the obj into DOM nodes
      }
    
      renderFromObject (object) {
        // you may need to do something fancier than appendChild,
        // you can always query the shadowRoot and insert it at
        // a specific point in shadow DOM
        this.shadowRoot.appendChild(this._xformObject(object));
      }
    }
    

    您当然必须注册自定义元素。

    现在有时候您真的无法摆脱做这样的事情。但这应该是绝对不得已。见下文:

    为什么我认为这是一个 bad 可疑的想法,以及如何使其变得更好:

    Web组件的主要吸引力之一是它启用声明性HTML标记,而不是过程性JS DOM操作。在提供您正在谈论的API的同时,肯定比例如通过创建表节点,创建行节点,创建一些tds,将其附加到行,然后将其附加到表中来创建表,我(并且我认为大多数)开发人员都认为,如果您的自定义元素需要用户直接操作 JavaScript,那么它实际上不是HTML元素:它是JavaScript接口。

    让我有一点资格。当我说“ requires” JavaScript时,我是说没有办法将其放入具有适当属性的页面上并最终得到想要的东西。当我说“直接”时,是指直接调用元素的对象表示方法,而不是说切换元素属性。将我的观点写在代码中:

    // good
    myCustomElement.setAttribute("toggled-on", true);
    
    // this isn't *bad*, but don't *force* people to do this
    myCustomElement.toggleState();
    

    您可能仍想在公共API中提供第二个内容,以方便用户使用,但是要求似乎面目全非。现在的问题是,您显然不能轻松地将复杂的数据结构传递给HTML属性(如果您使用的是Polymer,Polymer会提供帮助)。

    但是,如果不是这种情况,那么我将提供一个独立的函数来返回适当的DOM结构,而不是将其烘焙到元素中,而不是将其作为元素API的一部分。如果那样的话,您甚至可以将其设为自定义元素类的类方法。

    请考虑以下情况:您具有一个List元素,可以渲染任意数量的Item元素。我认为提供一种方便的方法很不错,该方法需要一个数组并更新(轻型)DOM。但是用户也应该能够直接将其附加。

    您的用例可能需要解决问题。有时候,您确实确实需要使用反模式。但请仔细考虑您的元素是否属于这种情况。

    答案 3 :(得分:0)

    这完全取决于您的情况。但通常,如果您发现自己被埋在嵌套的阴影根的地狱中,则可以考虑轻松使用阴影球。 如下例所示:

    <my-outer-element>
         shadowRoot
           <slot1> ---Reveal dom
           <my-inner-element>
              shadowRoot
                    ....