jhp*_*ING 2 html shadow-dom custom-element
根据自定义元素规范,
该元素不得获得任何属性或子元素,因为这违反了使用
createElement或createElementNS方法的消费者的期望。
在这种情况下,Firefox 和 Chrome 都会正确地抛出错误。但是,当附加影子 DOM 时,不会出现错误(在任一浏览器中)。
火狐浏览器:
NotSupportedError:不支持操作
铬合金:
未捕获的 DOMException:无法构造“CustomElement”:结果不得有子级
没有影子 DOM
function createElement(tag, ...children) {
let root;
if (typeof tag === 'symbol') {
root = document.createDocumentFragment();
} else {
root = document.createElement(tag);
}
children.forEach(node => root.appendChild(node));
return root;
}
customElements.define(
'x-foo',
class extends HTMLElement {
constructor() {
super();
this.appendChild(
createElement(
Symbol(),
createElement('div'),
),
);
}
},
);
createElement('x-foo');Run Code Online (Sandbox Code Playgroud)
使用影子 DOM
function createElement(tag, ...children) {
let root;
if (typeof tag === 'symbol') {
root = document.createDocumentFragment();
} else {
root = document.createElement(tag);
}
children.forEach(node => root.appendChild(node));
return root;
}
customElements.define(
'x-foo',
class extends HTMLElement {
constructor() {
super();
// it doesn't matter if this is open or closed
this.attachShadow({ mode: 'closed' }).appendChild(
createElement(
Symbol(),
createElement('div'),
),
);
}
},
);
createElement('x-foo');Run Code Online (Sandbox Code Playgroud)
请注意:为了查看示例,您需要(至少)使用以下设备之一:Firefox 63、Chrome 67、Safari 10.1。不支持边缘。
我的问题如下:
根据规范,所展示的行为是否正确?
向根添加子节点会导致 DOM 回流;如果没有影子 DOM,如何避免这种情况呢?
每次创建元素时都是通过构造函数完成的。但是,当调用构造函数时,没有子级也没有任何属性,这些都是在创建组件后添加的。
即使该元素是在 HTML 页面中定义的,它仍然是由使用构造函数的代码创建的,然后由解析 HTML 页面中 DOM 的代码添加属性和子元素。
调用构造函数时,没有子级,您无法添加它们,因为 DOM 解析器可能会在构造函数完成后立即添加它们。相同的规则适用于属性。
目前除了通过 JS 代码之外,无法指定 ShadowDOM 或 ShadowDOM 子元素。DOM 解析器不会向 ShadowDOM 添加任何子级。
因此,根据规范,在构造函数中访问、更改属性或子项或对其执行任何操作都是非法的。但是,由于 DOM 解析器无法将任何不非法的内容添加到组件 ShadowDOM 中。
当不使用 ShadowDOM 时,我通过使用在构造函数中创建的内部模板元素来解决这个问题,然后在调用时将其作为子元素放置connectedCallback。
// Class for `<test-el>`
class TestEl extends HTMLElement {
constructor() {
super();
console.log('constructor');
const template = document.createElement('template');
template.innerHTML = '<div class="name"></div>';
this.root = template.content;
this.rendered = false;
}
static get observedAttributes() {
return ['name'];
}
attributeChangedCallback(attrName, oldVal, newVal) {
if (oldVal !== newVal) {
console.log('attributeChangedCallback', newVal);
this.root.querySelector('.name').textContent = newVal;
}
}
connectedCallback() {
console.log('connectedCallback');
if (!this.rendered) {
this.rendered = true;
this.appendChild(this.root);
this.root = this;
}
}
// `name` property
get name() {
return this.getAttribute('name');
}
set name(value) {
console.log('set name', value);
if (value == null) { // Check for null or undefined
this.removeAttribute('name');
}
else {
this.setAttribute('name', value)
}
}
}
// Define our web component
customElements.define('test-el', TestEl);
const moreEl = document.getElementById('more');
const testEl = document.getElementById('test');
setTimeout(() => {
testEl.name = "Mummy";
const el = document.createElement('test-el');
el.name = "Frank N Stein";
moreEl.appendChild(el);
}, 1000);Run Code Online (Sandbox Code Playgroud)
<test-el id="test" name="Dracula"></test-el>
<hr/>
<div id="more"></div>Run Code Online (Sandbox Code Playgroud)
此代码在构造函数中创建一个模板并用于this.root引用它。一旦connectedCallback被调用,我将模板插入到 DOM 中并更改this.root为指向this,以便我对元素的所有引用仍然有效。
这是一种快速方法,可以让您的组件始终能够保持其子组件的正确性,而无需使用 ShadowDOM,并且仅在connectedCalback调用一次时将模板作为子组件放入 DOM 中。
| 归档时间: |
|
| 查看次数: |
5919 次 |
| 最近记录: |