我正在创建一个 自定义元素,它将能够将其内容从 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
返回一个空白,它应该返回 markdown;其他组合,例如 this.querySelector('mark-down')
,返回 null
。
如何获取自定义元素的内容?
我写了一篇(非常)长的 Dev.to 博客文章:
Web 组件开发人员还没有连接connectedCallback
最简单的解决方法是
setTimeout
中的
connectedCallback
<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 被完全解析。
setTimeout
在DOMContentLoaded
之后运行,但是如果您将所有内容包装在DOMContentLoaded
中,整个元素创建都会延迟,同样适用于defer
或将<script>
放在页面末尾
Supersharp 更好地解释了原因:
经过一些在线研究,我发现了以下关于connectedCallback
的要点:每次将自定义元素附加到文档连接元素中时。每次移动节点时都会发生这种情况,并且 可能在元素的内容完全解析之前发生。
因此,根据浏览器的不同,
innerHTML
在使用时实际上可能没有被定义。这就是为什么上面的代码片段虽然在 Firefox 中运行良好,但在 Chrome 或 Edge 中却不起作用。
要解决这个问题,请将
script
标签放在 body
的底部,在这种情况下,该元素将首先被解析,并且脚本将知道 innerHTML
包含什么。
解决此问题的另一种方法是将自定义元素构造函数包装在 DOM 加载事件中。该事件看起来像这样:
document.addEventListener('DOMContentLoaded', (e) => {
class markDown extends HTMLElement {
...
}
}
另一种方法是将脚本放入单独的文件中,并使用
script
属性标记 defer
标签。
无论类是否由单独的语句显式命名和定义(如 Triby 的答案所述),还是匿名并由自定义元素定义函数包装(如原始问题中所示),所有三种解决方案都有效。