如何获取自定义元素的内容

How to Get the Contents of a Custom Element

我正在创建一个 custom element,它将能够将其内容从 markdown 转换为 HTML。但是,我无法获取自定义元素的内容。

<!doctype html>
<html>
<body>
   <template id="mark-down">
      <div class="markdown"></div>
   </template>
   <!-- Converts markdown to HTML -->
   <script src="https://cdn.jsdelivr.net/gh/showdownjs/showdown/dist/showdown.js"></script>
   <script>
      customElements.define('mark-down',
         class extends HTMLElement {
            constructor() {
               super()
               let template = document.querySelector('#mark-down').content
               this.attachShadow({ mode: 'open' }).appendChild(template.cloneNode(true))
            }
            connectedCallback() {
               console.log(this) // Returns the whole <mark-down> node and its contents
               console.log(this.innerHTML) // So why does this return a blank string?
               // This should theoretically work --> let markdown = this.innerHTML
               let markdown = '## Test'
               let converter = new showdown.Converter()
               let html = converter.makeHtml(markdown)
               this.shadowRoot.innerHTML = html;
            }
         });
   </script>

   <main>
      <mark-down>
## Our Markdown

These contents should get converted

* One
* Two
* Three
      </mark-down>
   </main>
</body>
</html>

我的问题在 connectedCallback()。在记录 this 时,我得到了整个 <mark-down> 节点及其内容在 markdown 中。但是,它似乎没有有效的属性。使用 innerHTML returns 一个空白,它应该 return 降价;其他组合,例如 this.querySelector('mark-down')、return null.

如何获取自定义元素的内容?

在网上进行一些研究后,我发现 following nugget 关于 connectedCallback每次将自定义元素附加到文档连接元素中。每次移动节点时都会发生这种情况,并且可能会在元素的内容被完全解析之前发生

因此,根据浏览器的不同,innerHTML 在使用时可能实际上没有定义。这就是为什么上面的代码片段虽然在 Firefox 中很好,但在 Chrome 或 Edge 中不起作用。

为了解决这个问题,将script标签放在body的底部,这样元素会先被解析,脚本会知道innerHTML包含。

另一种解决方法是将自定义元素构造函数包装在 DOM 加载事件中。该事件看起来像这样:

document.addEventListener('DOMContentLoaded', (e) => {
   class markDown extends HTMLElement {
      ...
   }
}

另一种方法是将您的脚本放在一个单独的文件中,并使用 defer 属性标记 script 标签。

无论 class 是否由单独的语句显式命名和定义(如 Triby 的回答所述),或匿名并由自定义元素定义函数包装(如原始问题中所述),所有三种解决方案均有效.

这个咬了那么多,特意问了个Whosebug的问题让人找

最简单的解决方法是 setTimeoutconnectedCallback

<script>
  customElements.define('my-element', class extends HTMLElement {
    connectedCallback() {
      console.log(this.innerHTML);// "" in all Browsers
      setTimeout(() => {
        // now runs asap 
        console.log(this.innerHTML); // "A"
      });
    }
  })
</script>

<my-element>A</my-element>

这个和所有提到的解决方法所做的是推迟代码执行,直到 DOM 被完全解析。
setTimeoutDOMContentLoaded 之后运行,但是如果您将 所有内容 包装在 DOMContentLoaded 中,整个元素创建都会延迟,这同样适用于 defer 或将 <script> 放在页面末尾

Supersharp 更好地解释了原因: