面向 Web 开发者的版本 - 最后更新时间 2024 年 9 月 12 日
所有当前引擎都支持。
自定义元素 为作者提供了一种构建他们自己的完整功能的 DOM 元素的方法。虽然作者始终可以在其文档中使用非标准元素,并通过脚本或类似方法在之后添加特定于应用程序的行为,但此类元素历史上一直不符合标准,而且功能性不强。通过 定义 自定义元素,作者可以通知解析器如何正确构造元素以及该类元素应该如何对更改做出反应。
自定义元素是“平台合理化”更大努力的一部分,它通过解释现有平台功能(例如 HTML 的元素)来解释更低级别的作者公开可扩展性点(例如自定义元素定义)。尽管目前自定义元素的功能和语义方面存在很多限制,这些限制阻止它们完全解释 HTML 现有元素的行为,但我们希望随着时间的推移缩小这一差距。
为了说明如何创建一个 自主自定义元素,让我们定义一个自定义元素,它封装了渲染一个国家国旗的小图标。我们的目标是能够像这样使用它
< flag-icon country = "nl" ></ flag-icon >
为此,我们首先为自定义元素声明一个类,扩展 HTMLElement
class FlagIcon extends HTMLElement {
constructor() {
super ();
this . _countryCode = null ;
}
static observedAttributes = [ "country" ];
attributeChangedCallback( name, oldValue, newValue) {
// name will always be "country" due to observedAttributes
this . _countryCode = newValue;
this . _updateRendering();
}
connectedCallback() {
this . _updateRendering();
}
get country() {
return this . _countryCode;
}
set country( v) {
this . setAttribute( "country" , v);
}
_updateRendering() {
// Left as an exercise for the reader. But, you'll probably want to
// check this.ownerDocument.defaultView to see if we've been
// inserted into a document with a browsing context, and avoid
// doing any work if not.
}
}
然后,我们需要使用此类来定义元素
customElements. define( "flag-icon" , FlagIcon);
在这一点上,我们上面的代码将起作用!解析器在看到 flag-icon
标签时,将构造我们 FlagIcon
类的新的实例,并告诉我们的代码关于其新的 country
属性,然后我们使用该属性来设置元素的内部状态并更新其渲染(在适当的时候)。
您还可以使用 DOM API 创建 flag-icon
元素
const flagIcon = document. createElement( "flag-icon" )
flagIcon. country = "jp"
document. body. appendChild( flagIcon)
最后,我们还可以使用 自定义元素构造函数 本身。也就是说,上面的代码等同于
const flagIcon = new FlagIcon()
flagIcon. country = "jp"
document. body. appendChild( flagIcon)
添加一个带有 true 值的静态 formAssociated
属性,将 自主自定义元素 变成 与表单关联的自定义元素。ElementInternals
接口帮助您实现表单控件元素的常见函数和属性。
class MyCheckbox extends HTMLElement {
static formAssociated = true ;
static observedAttributes = [ 'checked' ];
constructor() {
super ();
this . _internals = this . attachInternals();
this . addEventListener( 'click' , this . _onClick. bind( this ));
}
get form() { return this . _internals. form; }
get name() { return this . getAttribute( 'name' ); }
get type() { return this . localName; }
get checked() { return this . hasAttribute( 'checked' ); }
set checked( flag) { this . toggleAttribute( 'checked' , Boolean( flag)); }
attributeChangedCallback( name, oldValue, newValue) {
// name will always be "checked" due to observedAttributes
this . _internals. setFormValue( this . checked ? 'on' : null );
}
_onClick( event) {
this . checked = ! this . checked;
}
}
customElements. define( 'my-checkbox' , MyCheckbox);
您可以将自定义元素 my-checkbox
像内置的与表单关联的元素一样使用。例如,将它放在 form
或 label
中,会将 my-checkbox
元素与它们相关联,提交 form
将发送由 my-checkbox
实现提供的数据。
< form action = "..." method = "..." >
< label >< my-checkbox name = "agreed" ></ my-checkbox > I read the agreement.</ label >
< input type = "submit" >
</ form >
通过使用 ElementInternals
的适当属性,您的自定义元素可以具有默认的可访问性语义。以下代码扩展了我们上一节中的与表单关联的复选框,以正确设置其默认角色和选中状态,如辅助技术所见
class MyCheckbox extends HTMLElement {
static formAssociated = true ;
static observedAttributes = [ 'checked' ];
constructor() {
super ();
this . _internals = this . attachInternals();
this . addEventListener( 'click' , this . _onClick. bind( this ));
this . _internals. role = 'checkbox' ;
this . _internals. ariaChecked = 'false' ;
}
get form() { return this . _internals. form; }
get name() { return this . getAttribute( 'name' ); }
get type() { return this . localName; }
get checked() { return this . hasAttribute( 'checked' ); }
set checked( flag) { this . toggleAttribute( 'checked' , Boolean( flag)); }
attributeChangedCallback( name, oldValue, newValue) {
// name will always be "checked" due to observedAttributes
this . _internals. setFormValue( this . checked ? 'on' : null );
this . _internals. ariaChecked = this . checked;
}
_onClick( event) {
this . checked = ! this . checked;
}
}
customElements. define( 'my-checkbox' , MyCheckbox);
请注意,与内置元素一样,这些只是默认值,可以由页面作者使用 role
和 aria-*
属性覆盖
<!-- This markup is non-conforming -->
< input type = "checkbox" checked role = "button" aria-checked = "false" >
<!-- This markup is probably not what the custom element author intended -->
< my-checkbox role = "button" checked aria-checked = "false" >
鼓励自定义元素作者说明其可访问性语义的哪些方面是强原生语义,即不应由自定义元素的用户覆盖。在我们的示例中,my-checkbox
元素的作者会声明其 role 和 aria-checked
值是强原生语义,因此不鼓励使用类似上面的代码。
自定义内置元素 是一种与众不同的 自定义元素,其定义方式略有不同,使用方式也与 自主自定义元素 相比有很大不同。它们的存在是为了允许重用 HTML 现有元素的行为,方法是将这些元素扩展为具有新的自定义功能。这很重要,因为 HTML 元素的许多现有行为不幸的是无法通过纯粹使用 自主自定义元素 来复制。相反,自定义内置元素 允许在现有元素上安装自定义构造行为、生命周期钩子以及原型链,本质上是在现有元素之上“混合”这些功能。
自定义内置元素 需要与 自主自定义元素 不同的语法,因为用户代理和其他软件依赖元素的本地名称来识别元素的语义和行为。也就是说,自定义内置元素 在现有行为之上构建的概念,关键在于扩展的元素保留其原始本地名称。
在此示例中,我们将创建一个名为 plastic-button
的 自定义内置元素,它像普通按钮一样,但在您单击它时会添加花哨的动画效果。我们首先定义一个类,就像之前一样,虽然这次我们扩展了 HTMLButtonElement
而不是 HTMLElement
class PlasticButton extends HTMLButtonElement {
constructor() {
super ();
this . addEventListener( "click" , () => {
// Draw some fancy animation effects!
});
}
}
定义自定义元素时,我们还必须指定 extends
选项
customElements. define( "plastic-button" , PlasticButton, { extends : "button" });
通常,扩展的元素名称不能仅通过查看它扩展的元素接口来确定,因为许多元素共享相同的接口(例如,q
和 blockquote
都共享 HTMLQuoteElement
)。
要从解析的 HTML 源文本构造我们的 自定义内置元素,我们使用 is
属性在 button
元素上
< button is = "plastic-button" > Click Me!</ button >
尝试将 自定义内置元素 作为 自主自定义元素 使用,将不会起作用;也就是说,<plastic-button>Click me?</plastic-button>
仅创建一个没有特殊行为的 HTMLElement
。
如果您需要以编程方式创建一个自定义内置元素,可以使用以下形式的 createElement()
const plasticButton = document. createElement( "button" , { is: "plastic-button" });
plasticButton. textContent = "Click me!" ;
和之前一样,构造函数也可以使用
const plasticButton2 = new PlasticButton();
console. log( plasticButton2. localName); // will output "button"
console. assert( plasticButton2 instanceof PlasticButton);
console. assert( plasticButton2 instanceof HTMLButtonElement);
请注意,以编程方式创建自定义内置元素时,is
属性将不会出现在 DOM 中,因为它没有显式设置。但是,在序列化时会将其添加到输出
console. assert( ! plasticButton. hasAttribute( "is" ));
console. log( plasticButton. outerHTML); // will output '<button is="plastic-button"></button>'
无论它如何创建,button
的所有特殊方式都适用于此类“塑料按钮”:它们的焦点行为、参与表单提交 的能力、disabled
属性等等。
自定义内置元素 旨在允许扩展具有有用的用户代理提供的行为或 API 的现有 HTML 元素。因此,它们只能扩展此规范中定义的现有 HTML 元素,而不能扩展诸如 bgsound
、blink
、isindex
、keygen
、multicol
、nextid
或 spacer
这些已定义为使用 HTMLUnknownElement
作为其 元素接口 的旧元素。
此要求的原因之一是未来的兼容性:如果定义了一个 自定义内置元素,它扩展了当前未知的元素,例如 combobox
,这将阻止此规范在将来定义 combobox
元素,因为派生 自定义内置元素 的使用者将开始依赖其基本元素没有有趣的用户代理提供的行为。
如以下所述以及上面提到的,仅仅定义和使用一个名为taco-button
的元素并不意味着这种元素代表按钮。也就是说,诸如网络浏览器、搜索引擎或辅助技术之类的工具不会仅仅根据其定义的名称自动将生成的元素视为按钮。
为了向各种用户传达所需的按钮语义,同时仍然使用一个自主自定义元素,需要采用多种技术。
添加tabindex
属性将使taco-button
可获得焦点。请注意,如果taco-button
在逻辑上被禁用,则需要删除tabindex
属性。
添加 ARIA 角色以及各种 ARIA 状态和属性有助于向辅助技术传达语义。例如,将role设置为“button
”将传达这是一个按钮的语义,使用户能够通过其辅助技术中的通常按钮式交互成功地与控件进行交互。设置aria-label
属性是必要的,以便为按钮提供一个可访问名称,而不是让辅助技术遍历其子文本节点并宣布它们。当按钮在逻辑上被禁用时,将aria-disabled
状态设置为“true
”,向辅助技术传达按钮的禁用状态。
添加事件处理程序以处理通常期望的按钮行为有助于向网络浏览器用户传达按钮的语义。在这种情况下,最相关的事件处理程序是将适当的keydown
事件代理为click
事件,这样您就可以通过键盘和单击来激活按钮。
除了为taco-button
元素提供的任何默认视觉样式外,还需要更新视觉样式以反映逻辑状态的变化,例如变为禁用;也就是说,任何具有taco-button
规则的样式表也需要具有taco-button[disabled]
的规则。
考虑到这些要点,一个全功能的taco-button
,它承担了传达按钮语义的责任(包括能够被禁用),可能看起来像这样。
class TacoButton extends HTMLElement {
static observedAttributes = [ "disabled" ];
constructor() {
super ();
this . _internals = this . attachInternals();
this . _internals. role = "button" ;
this . addEventListener( "keydown" , e => {
if ( e. code === "Enter" || e. code === "Space" ) {
this . dispatchEvent( new PointerEvent( "click" , {
bubbles: true ,
cancelable: true
}));
}
});
this . addEventListener( "click" , e => {
if ( this . disabled) {
e. preventDefault();
e. stopImmediatePropagation();
}
});
this . _observer = new MutationObserver(() => {
this . _internals. ariaLabel = this . textContent;
});
}
connectedCallback() {
this . setAttribute( "tabindex" , "0" );
this . _observer. observe( this , {
childList: true ,
characterData: true ,
subtree: true
});
}
disconnectedCallback() {
this . _observer. disconnect();
}
get disabled() {
return this . hasAttribute( "disabled" );
}
set disabled( flag) {
this . toggleAttribute( "disabled" , Boolean( flag));
}
attributeChangedCallback( name, oldValue, newValue) {
// name will always be "disabled" due to observedAttributes
if ( this . disabled) {
this . removeAttribute( "tabindex" );
this . _internals. ariaDisabled = "true" ;
} else {
this . setAttribute( "tabindex" , "0" );
this . _internals. ariaDisabled = "false" ;
}
}
}
即使有了这个相当复杂的元素定义,该元素对于使用者来说也不是很方便:它会不断地“萌发”tabindex
属性,并且它选择tabindex="0"
可获得焦点行为可能与当前平台上的button
行为不匹配。这是因为截至目前,还没有办法为自定义元素指定默认的焦点行为,迫使使用tabindex
属性来做到这一点(即使它通常用于允许消费者覆盖默认行为)。
相比之下,一个简单的定制内置元素,如上一节所示,将自动继承button
元素的语义和行为,无需手动实现这些行为。一般来说,对于任何基于 HTML 中现有元素构建的具有非平凡行为和语义的元素,定制内置元素将更容易开发、维护和使用。
由于元素定义可以在任何时候发生,因此非自定义元素可以创建,然后在注册适当的定义后成为自定义元素。我们将此过程称为将元素从普通元素升级为自定义元素。
升级允许在可能更希望在相关元素最初创建后(例如,通过解析器)注册自定义元素定义的场景中使用。它们允许对自定义元素中的内容进行渐进式增强。例如,在以下 HTML 文档中,img-viewer
的元素定义是异步加载的
<!DOCTYPE html>
< html lang = "en" >
< title > Image viewer example</ title >
< img-viewer filter = "Kelvin" >
< img src = "images/tree.jpg" alt = "A beautiful tree towering over an empty savannah" >
</ img-viewer >
< script src = "js/elements/img-viewer.js" async ></ script >
这里的img-viewer
元素的定义是使用一个标记为async
属性的script
元素加载的,该元素放置在标记中的<img-viewer>
标签之后。当脚本正在加载时,img-viewer
元素将被视为未定义的元素,类似于span
。一旦脚本加载,它将定义img-viewer
元素,并且页面上现有的img-viewer
元素将被升级,应用自定义元素的定义(该定义可能包括应用由字符串“Kelvin”标识的图像过滤器,增强图像的视觉外观)。
请注意,升级仅适用于文档树中的元素。(正式地说,是连接的元素。)未插入文档中的元素将保持未升级状态。以下示例说明了这一点
<!DOCTYPE html>
< html lang = "en" >
< title > Upgrade edge-cases example</ title >
< example-element ></ example-element >
< script >
"use strict" ;
const inDocument = document. querySelector( "example-element" );
const outOfDocument = document. createElement( "example-element" );
// Before the element definition, both are HTMLElement:
console. assert( inDocument instanceof HTMLElement);
console. assert( outOfDocument instanceof HTMLElement);
class ExampleElement extends HTMLElement {}
customElements. define( "example-element" , ExampleElement);
// After element definition, the in-document element was upgraded:
console. assert( inDocument instanceof ExampleElement);
console. assert( ! ( outOfDocument instanceof ExampleElement));
document. body. appendChild( outOfDocument);
// Now that we've moved the element into the document, it too was upgraded:
console. assert( outOfDocument instanceof ExampleElement);
</ script >
用户代理提供的内置元素具有某些状态,这些状态会随着时间的推移而发生变化,具体取决于用户交互和其他因素,并且通过伪类公开给 Web 作者。例如,某些表单控件具有“invalid”状态,该状态通过:invalid
伪类公开。
与内置元素类似,自定义元素也可以处于各种状态,并且自定义元素作者希望以类似于内置元素的方式公开这些状态。
这是通过:state()
伪类完成的。自定义元素作者可以使用ElementInternals
的states
属性来添加和删除此类自定义状态,这些状态随后会作为:state()
伪类的参数公开。
以下示例显示了如何使用:state()
来样式化自定义复选框元素。假设LabeledCheckbox
没有通过内容属性公开其“checked”状态。
< script >
class LabeledCheckbox extends HTMLElement {
constructor() {
super ();
this . _internals = this . attachInternals();
this . addEventListener( 'click' , this . _onClick. bind( this ));
const shadowRoot = this . attachShadow({ mode: 'closed' });
shadowRoot. innerHTML =
`<style>
:host::before {
content: '[ ]';
white-space: pre;
font-family: monospace;
}
:host(:state(checked))::before { content: '[x]' }
</style>
<slot>Label</slot>` ;
}
get checked() { return this . _internals. states. has( 'checked' ); }
set checked( flag) {
if ( flag)
this . _internals. states. add( 'checked' );
else
this . _internals. states. delete ( 'checked' );
}
_onClick( event) {
this . checked = ! this . checked;
}
}
customElements. define( 'labeled-checkbox' , LabeledCheckbox);
</ script >
< style >
labeled-checkbox { border : dashed red ; }
labeled-checkbox : state ( checked ) { border : solid ; }
</ style >
< labeled-checkbox > You need to check this</ labeled-checkbox >
自定义伪类甚至可以定位影子部分。上面的示例的扩展展示了这一点
< script >
class QuestionBox extends HTMLElement {
constructor() {
super ();
const shadowRoot = this . attachShadow({ mode: 'closed' });
shadowRoot. innerHTML =
`<div><slot>Question</slot></div>
<labeled-checkbox part='checkbox'>Yes</labeled-checkbox>` ;
}
}
customElements. define( 'question-box' , QuestionBox);
</ script >
< style >
question-box :: part ( checkbox ) { color : red ; }
question-box :: part ( checkbox ) : state ( checked ) { color : green ; }
</ style >
< question-box > Continue?</ question-box >
在编写自定义元素构造函数时,作者受以下一致性要求的约束
对super()
的无参数调用必须是构造函数体中的第一个语句,以便在运行任何其他代码之前建立正确的原型链和this值。
return
语句不得出现在构造函数体中的任何地方,除非它是一个简单的提前返回(return
或return this
)。
构造函数不得使用document.write()
或document.open()
方法。
不得检查元素的属性和子元素,因为在非升级情况下,将不存在任何属性和子元素,并且依赖升级会降低元素的可使用性。
元素不得获得任何属性或子元素,因为这违反了使用createElement
或createElementNS
方法的消费者的期望。
一般来说,应尽可能将工作推迟到connectedCallback
中,尤其是涉及获取资源或渲染的工作。但是,请注意,connectedCallback
可以被调用多次,因此任何真正一次性的初始化工作都需要一个保护措施以防止它运行两次。
一般来说,构造函数应用于设置初始状态和默认值,以及设置事件监听器,并可能设置一个影子根。
这些要求中的几个在元素创建期间被直接或间接地检查,未能遵守这些要求将导致无法通过解析器或 DOM API 实例化的自定义元素。即使工作是在构造函数启动的微任务中完成的,情况也是如此,因为在构造之后可能立即发生微任务检查点。
在编写自定义元素反应时,作者应避免操作节点树,因为这会导致意外结果。
元素的connectedCallback
可以在元素断开连接之前排队,但由于回调队列仍在处理,因此会导致对不再连接的元素进行connectedCallback
。
class CParent extends HTMLElement {
connectedCallback() {
this . firstChild. remove();
}
}
customElements. define( "c-parent" , CParent);
class CChild extends HTMLElement {
connectedCallback() {
console. log( "CChild connectedCallback: isConnected =" , this . isConnected);
}
}
customElements. define( "c-child" , CChild);
const parent = new CParent(),
child = new CChild();
parent. append( child);
document. body. append( parent);
// Logs:
// CChild connectedCallback: isConnected = false
一个自定义元素是一个自定义的元素。非正式地说,这意味着它的构造函数和原型是由作者定义的,而不是由用户代理定义的。此作者提供的构造函数称为自定义元素构造函数。
可以定义两种不同类型的自定义元素
一个自主自定义元素,它是在没有extends
选项的情况下定义的。这些类型的自定义元素的本地名称与其定义的名称相同。
一个定制内置元素,它是在有extends
选项的情况下定义的。这些类型的自定义元素的本地名称等于其extends
选项中传递的值,并且它们的定义的名称用作is
属性的值,因此必须是有效的自定义元素名称。
自主自定义元素具有以下元素定义
is
属性form
,对于 与表单关联的自定义元素 — 将元素与 form
元素关联disabled
,对于 与表单关联的自定义元素 — 是否禁用表单控件readonly
,对于 与表单关联的自定义元素 — 影响 willValidate
,以及自定义元素作者添加的任何行为name
,对于 与表单关联的自定义元素 — 用于 表单提交 和 form.elements
API 的元素名称HTMLElement
)自主自定义元素 没有特殊含义:它 代表 其子元素。一个 定制内置元素 继承了它所扩展的元素的语义。
任何与元素功能相关的无命名空间属性,由元素作者确定,都可以指定在 自主自定义元素 上,只要属性名称是 XML 兼容 并且不包含 ASCII 大写字母。例外是 is
属性,它不能指定在 自主自定义元素 上(并且如果存在,它将不起作用)。
定制内置元素 遵循属性的正常要求,基于它们扩展的元素。要添加基于属性的自定义行为,请使用 data-*
属性。
一个 自主自定义元素 如果与 自定义元素定义 关联,该定义的 form-associated 字段设置为 true,则称为 与表单关联的自定义元素。
name
属性代表 与表单关联的自定义元素 的名称。 disabled
属性用于使 与表单关联的自定义元素 无法交互并阻止其 提交值 提交。 form
属性用于显式地将 与表单关联的自定义元素 与其 表单所有者 关联。
与表单关联的自定义元素 的 readonly
属性指定元素 禁止约束验证。用户代理不会为该属性提供任何其他行为,但自定义元素作者应该在可能的情况下使用其存在来以某种适当的方式使他们的控件不可编辑,类似于内置表单控件上 readonly 属性的行为。
约束验证:如果 readonly
属性指定在 与表单关联的自定义元素 上,则元素 禁止约束验证。
与表单关联的自定义元素 的 重置算法 是 将一个自定义元素回调反应排队 与元素、回调名称“formResetCallback
”和一个空参数列表。
有效的自定义元素名称 是满足以下所有要求的字符序列 name
name 必须与 PotentialCustomElementName
产生式匹配
PotentialCustomElementName ::=
[a-z] (PCENChar)* '-' (PCENChar)*
PCENChar ::=
"-" | "." | [0-9] | "_" | [a-z] | #xB7 | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x203F-#x2040] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF]
name 必须不是以下任何名称
annotation-xml
color-profile
font-face
font-face-src
font-face-uri
font-face-format
font-face-name
missing-glyph
上面的名称列表是来自 适用规范(即 SVG 2 和 MathML)的所有包含连字符的元素名称的摘要。 [SVG] [MATHML]
这些要求确保了 有效的自定义元素名称 的一些目标
它们以一个 ASCII 小写字母 开头,确保 HTML 解析器将它们视为标签而不是文本。
它们不包含任何 ASCII 大写字母,确保用户代理可以始终以不区分大小写的 ASCII 方式处理 HTML 元素。
它们包含一个连字符,用于命名空间并确保向前兼容性(因为将来不会在 HTML、SVG 或 MathML 中添加包含连字符的局部名称的元素)。
它们总是可以通过 createElement()
和 createElementNS()
创建,它们具有超出解析器范围的限制。
除了这些限制之外,还允许大量名称,以最大限度地提高像 <math-α>
或 <emotion-😍>
这样的用例的灵活性。
CustomElementRegistry
接口所有当前引擎都支持。
自定义元素注册表与 Window
对象而不是 Document
对象关联,因为每个 自定义元素构造函数 继承自 HTMLElement
接口,并且每个 Window
对象只有一个 HTMLElement
接口。
window.customElements.define(name, constructor)
window.customElements.define(name, constructor, { extends: baseLocalName })
NotSupportedError
" DOMException
。window.customElements.get(name)
window.customElements.getName(constructor)
window.customElements.whenDefined(name)
SyntaxError
" DOMException
被拒绝的 Promise。window.customElements.upgrade(root)
元素定义 是将 自定义元素定义 添加到 CustomElementRegistry
的过程。这是通过 define()
方法实现的。
whenDefined()
方法可用于避免在所有适当的 自定义元素 定义 之前执行操作。在此示例中,我们将它与 :defined
伪类结合使用,以隐藏动态加载的文章内容,直到我们确定它使用的所有 自主自定义元素 都已定义。
articleContainer. hidden = true ;
fetch( articleURL)
. then( response => response. text())
. then( text => {
articleContainer. innerHTML = text;
return Promise. all(
[... articleContainer. querySelectorAll( ":not(:defined)" )]
. map( el => customElements. whenDefined( el. localName))
);
})
. then(() => {
articleContainer. hidden = false ;
});
upgrade()
方法允许根据需要升级元素。通常,元素在变成 连接 时会自动升级,但如果您需要在准备好连接元素之前进行升级,可以使用此方法。
const el = document. createElement( "spider-man" );
class SpiderMan extends HTMLElement {}
customElements. define( "spider-man" , SpiderMan);
console. assert( ! ( el instanceof SpiderMan)); // not yet upgraded
customElements. upgrade( el);
console. assert( el instanceof SpiderMan); // upgraded!
一个 自定义元素 能够通过运行作者代码来响应某些事件。
当 升级 时,会运行它的 构造函数,不带任何参数。
当它 连接 时,会调用它的 connectedCallback
,不带任何参数。
当它 断开连接 时,会调用它的 disconnectedCallback
,不带任何参数。
当它被 采用 到新的文档中时,会调用它的 adoptedCallback
,并提供旧文档和新文档作为参数。
当它的任何属性被 更改、追加、移除 或 替换 时,会调用它的 attributeChangedCallback
,并提供属性的本地名称、旧值、新值和命名空间作为参数。(当属性分别被添加或删除时,属性的旧值或新值被认为是 null。)
当用户代理 重置表单所有者 的 与表单关联的自定义元素 并且这样做会更改表单所有者时,会调用它的 formAssociatedCallback
,并提供新的表单所有者(如果没有所有者则为 null)作为参数。
当 与表单关联的自定义元素 的表单所有者被 重置 时,会调用它的 formResetCallback
。
当 与表单关联的自定义元素 的 禁用 状态发生变化时,会调用它的 formDisabledCallback
,并提供新的状态作为参数。
当用户代理代表用户更新 与表单关联的自定义元素 的值,或者 作为导航的一部分 时,会调用它的 formStateRestoreCallback
,并提供新的状态和一个表示原因的字符串,"autocomplete
" 或 "restore
",作为参数。
我们将这些反应统称为 自定义元素反应。
调用 自定义元素反应 的方式非常小心,以避免在执行复杂操作时运行作者代码。实际上,它们会被延迟到“在返回用户脚本之前”。这意味着对于大多数目的,它们看起来是同步执行的,但在复杂复合操作(如 克隆 或 范围 操作)的情况下,它们将被延迟到所有相关的用户代理处理步骤完成后,然后作为一个批次一起运行。
保证 自定义元素反应 始终以与其触发操作相同的顺序调用,至少在单个 自定义元素 的本地上下文中是如此。(因为 自定义元素反应 代码可以执行自己的变异,所以无法对多个元素进行全局排序保证。)
某些功能旨在供自定义元素作者使用,但不供自定义元素使用者使用。这些功能由 element.attachInternals()
方法提供,该方法返回一个 ElementInternals
实例。ElementInternals
的属性和方法允许控制用户代理提供给所有元素的内部功能。
element.attachInternals()
所有当前引擎都支持。
返回一个针对 自定义元素 element 的 ElementInternals
对象。如果 element 不是 自定义元素,如果 "internals
" 功能在元素定义中被禁用,或者如果在同一个元素上调用两次,则会抛出异常。
internals.shadowRoot
如果 internals 的 目标元素 是 阴影宿主,则返回 ShadowRoot
,否则返回 null。
internals.setFormValue(value)
将 internals 的 目标元素 的 状态 和 提交值 都设置为 value。
如果 value 为 null,则元素不会参与表单提交。
internals.setFormValue(value, state)
将 internals 的 目标元素 的 提交值 设置为 value,并将它的 状态 设置为 state。
如果 value 为 null,则元素不会参与表单提交。
internals.form
返回 internals 的 目标元素 的 表单所有者。
internals.setValidity(flags, message [, anchor ])
标记 internals 的 目标元素 遭受由 flags 参数指示的约束,并将元素的验证消息设置为 message。如果指定了 anchor,用户代理可能会使用它来指示当 表单所有者 以交互方式验证或调用 reportValidity()
时,internals 的 目标元素 约束存在问题。
internals.setValidity({})
标记 internals 的 目标元素 满足其约束。
internals.willValidate
如果 internals 的 目标元素 在表单提交时将被验证,则返回 true;否则返回 false。
internals.validity
返回 internals 的 目标元素 的 ValidityState
对象。
internals.validationMessage
返回如果 internals 的 目标元素 要进行有效性检查,将向用户显示的错误消息。
valid = internals.checkValidity()
如果 internals 的 目标元素 没有有效性问题,则返回 true;否则返回 false。在后一种情况下,在元素上触发一个 invalid
事件。
valid = internals.reportValidity()
如果 internals 的 目标元素 没有有效性问题,则返回 true;否则返回 false,在元素上触发一个 invalid
事件,并且(如果事件没有被取消)向用户报告问题。
internals.labels
每个 与表单关联的自定义元素 都有一个 提交值。它用于在表单提交时提供一个或多个 条目。提交值 的初始值为 null,提交值 可以是 null、字符串、File
或 列表,其中包含 条目。
每个 与表单关联的自定义元素 都有一个 状态。它是用户代理可以使用它来恢复元素中用户输入的信息。状态 的初始值为 null,状态 可以是 null、字符串、File
或 列表,其中包含 条目。
setFormValue()
方法由自定义元素作者用来设置元素的 提交值 和 状态,从而将这些值传达给用户代理。
当用户代理认为恢复 与表单关联的自定义元素 的 状态 是一个好主意时,例如 导航后 或重新启动用户代理,他们可能会 将一个自定义元素回调反应入队,其中包含该元素、回调名称 "formStateRestoreCallback
"、一个包含要恢复的状态的参数列表和 "restore
"。
如果用户代理具有表单填充辅助功能,则当该功能被调用时,它可以将自定义元素回调反应入队,并带有一个与表单关联的自定义元素,回调名称为“formStateRestoreCallback
”,一个包含由状态值历史记录和一些启发式方法确定的状态值的参数列表,以及“autocomplete
”。
通常,状态是由用户指定的信息,而提交值是在规范化或清理之后的值,适用于提交到服务器。以下示例将使这一点具体化
假设我们有一个与表单关联的自定义元素,它要求用户指定日期。用户指定了"3/15/2019",但控件希望将"2019-03-15"
提交到服务器。 "3/15/2019"将是元素的状态,而"2019-03-15"
将是提交值。
假设您开发了一个模拟现有复选框输入
类型的自定义元素。其提交值将是其value
内容属性的值,或字符串"on"
。其状态将是"checked"
、"unchecked"
、"checked/indeterminate"
或"unchecked/indeterminate"
之一。
internals.role [ = value ]
设置或检索internals的目标元素的默认 ARIA 角色,除非页面作者使用role
属性覆盖它,否则将使用此角色。
internals.aria* [ = value ]
设置或检索internals的目标元素的各种默认 ARIA 状态或属性值,除非页面作者使用aria-*
属性覆盖它们,否则将使用这些值。
通过使用ElementInternals
的role
和aria*
属性,自定义元素作者可以为其自定义元素设置默认的可访问角色、状态和属性值,类似于原生元素的行为。有关更多详细信息,请参阅上面的示例。
internals.states.add(value)
将字符串value添加到元素的状态集中,以便将其作为伪类公开。
internals.states.has(value)
如果元素的状态集中包含value,则返回 true,否则返回 false。
internals.states.delete(value)
如果元素的状态集包含value,则将其删除并返回 true。否则,将返回 false。
internals.states.clear()
从元素的状态集中删除所有值。
for (const stateName of internals.states)
for (const stateName of internals.states.entries())
for (const stateName of internals.states.keys())
for (const stateName of internals.states.values())
迭代元素的状态集中的所有值。
internals.states.forEach(callback)
通过对每个值调用callback一次,迭代元素的状态集中的所有值。
internals.states.size
返回元素的状态集中的值数量。
状态集可以公开由字符串值的出现/不存在表示的布尔状态。如果作者希望公开具有三个值的 state,则可以将其转换为三个互斥的布尔状态。例如,名为readyState
的状态,其值为"loading"
、"interactive"
和"complete"
,可以映射到三个互斥的布尔状态:"loading"
、"interactive"
和"complete"
。
// Change the readyState from anything to "complete".
this . _readyState = "complete" ;
this . _internals. states. delete ( "loading" );
this . _internals. states. delete ( "interactive" );
this . _internals. states. add( "complete" );