没有特性的 HTML 元素
一般常使用标准的 HTML 元素,如:<p>欢迎进入我的博客</p>
组织 DOM。
其实,可以使用自定义的 HTML 元素,
比如:<greeting>欢迎进入我的博客</greeting>
可以在浏览器正常显示:
这里 <greeting> 便是
非标准的 HTML 元素。可以添加自定义样式:
<style>
greeting {
display: block;
font-size: 36px;
color: red;
}
</style>
运行结果如下:
接着,使用脚本操作这个元素:
<script>
'use strict'
let elements = document.querySelectorAll('greeting')
greeting_elem = elements[0]
greeting_elem.innerText = '你好,世界'
</script>
运行结果如下:
此例子说明,浏览器对待自定义元素,就像对待标准元素一样,只是没有默认的样式和行为。换言之,浏览器必须将自定义元素保留在 DOM 之中,但不会有任何语义信息和附加属性。除此之外,自定义元素与标准元素是一样的。
其实,所有的自定义元素都是HTMLUnknownElement
对象的实例。
标准 custom elements
为了让自定义的 HTML 元素支持一些 HTML 的内置属性,custom element 名称必须包括至少一个短横线 -,比如
my-element 和
super-button
都是有效的元素名,但
myelement 并不是。
这是为了确保 custom element 和内置 HTML 元素之间不会发生命名冲突。
此时自定义元素就不是HTMLUnknownElement
的实例了,而是HTMLElement 的实例。
<script>
'use strict'
let helloWorld = document.createElement('hello-world')
helloWorld instanceof HTMLUnknownElement // 输出 false
helloWorld instanceof HTMLElement // 输出 true
</script>
注意这里的
let helloWorld = document.createElement('hello-world')
等价于在 HTML 中的
<hello-world></hello-world>
换言之,
document.createElement 函数用于创建一个由标签名称
tagName 指定的 HTML 元素。
创建 autonomous custom elements
可以通过 JavaScript 的 class 定义 HTML 的标准自定义元素。
比如,可以定义一个带有统一样式的标识代码的 HTML 元素。
<script>
'use strict'
class InlineCode extends HTMLElement {
constructor() {
super()
this.classList.add('w3-card', 'w3-padding-small', 'w3-round-xxlarge', 'w3-light-gray')
}
}
customElements.define("inline-code", InlineCode)
</script>
这样,便可以在 HTML 中以如,<inline-code>Alt</inline-code> 的方式使用自定义元素。
这种方式的自定义元素被称为自主定制元素(Autonomous custom element)。
注意:自主自定义元素的构造函数必须扩展 HTMLElement。
创建 customized built-in elements
Customized built-in elements 继承自内置的或者已有的 HTML 元素。在创建时,你必须指定所需扩展的元素,
使用时,需要先写出基本的元素标签,并通过 is 属性指定 custom element 的名称。
例如 <p is="word-count">。
更加详细的是如下例子:
'use strict'
class BlockCode extends HTMLPreElement {
constructor() {
super()
this.classList.add('w3-card', 'w3-code', 'w3-pale-green',
'w3-padding-small', 'w3-round-xlarge', 'w3-text-purple')
}
}
customElements.define("block-code", BlockCode, {extends: 'pre'})
在 HTML 中便可像下面的方式定义代码块:
<pre is='block-code'>
<code>
...
</code>
</pre>
创建 shadow DOM
web components 的应该重要的特性就是封装:可以将 HTML 元素,样式和行为隐藏,并与其他代码项隔离。实现该特性的手段就是 shadow DOM。
// 为新元素创建一个类
class WordCount extends HTMLParagraphElement {
constructor() {
// 在构造器中先调用一下 super
super()
// 创建一个 shadow root
const shadow = this.attachShadow({ mode: 'open' })
//创建文本节点并向其添加计数器
this.text = document.createElement('span')
this.updateCount()
//将其添加到shadow root上
shadow.appendChild(this.text)
//当元素内容发生变化时更新计数
setInterval(this.updateCount, 200)
}
countWords(node) { // 统计英文单词数目
const text = node.innerText || node.textContent
return text.split(/\s+/g).length
}
updateCount() {
// 单词计数器指向元素的父级
const wcParent = this.parentNode
const count = `Words: ${this.countWords(wcParent)}`
this.text.textContent = count
}
}
// 定义新元素
customElements.define('word-count', WordCount, { extends: 'p' })
只需要在你需要统计的 HTML 元素中嵌入 <p is="word-count"></p> 即可统计该元素内的空白符格式。
使用 <template>
下面以创建一个用户卡片为例介绍如何使用 <template> 保存一份可复用的 HTML 元素。首先,在
HTML 中创建一个模板:
<template class="userCardTemplate">
<!-- 引入外部 CSS -->
<link rel='stylesheet' href="styles/user-card.css">
<!-- 定义图片 -->
<img class="image" alt="user card">
<div class="container">
<!-- 定义用户基本信息 -->
<p class="name"></p>
<p class="email"></p>
<button class="button">Follow John</button>
</div>
</template>
接着,使用 JavaScript 创建 HTML 自定义元素:
class UserCard extends HTMLElement {
constructor() {
super()
let shadow = this.attachShadow({ mode: 'open' })
let templateElem = document.querySelector('.userCardTemplate')
let content = templateElem.content.cloneNode(true)
content.querySelector('img').setAttribute('src', this.getAttribute('image'))
content.querySelector('.container>.name').innerText = this.getAttribute('name')
content.querySelector('.container>.email').innerText = this.getAttribute('email')
shadow.appendChild(content)
}
}
customElements.define("user-card", UserCard)
然后,便可以在 HTML 自如的使用该元素:
<user-card image="your-card.png" name="User Name" email="yourmail@some-email.com">
</user-card>
可以看到使用 <template> 极大的简化了代码。
使用 <slot>
虽然 <template> 已经为我们带来了便利,但是还是不够友好,下面我们使用 <slot> 作为一个占位符,重新定义用户卡片模板:
<template class="userCardTemplate">
<link rel='stylesheet' href="styles/user-card.css">
<img class="image" alt="user card">
<div class="container">
<p><slot name='name'>User Name</slot>
<p><slot name='email'>yourmail@some-email.com</slot>
<button class="button">Follow John</button>
</div>
</template>
这时,JavaScript 便可以这样:
class UserCard extends HTMLElement {
constructor() {
super()
let shadow = this.attachShadow({ mode: 'open' })
let templateElem = document.querySelector('.userCardTemplate')
let content = templateElem.content.cloneNode(true)
content.querySelector('img').setAttribute('src', this.getAttribute('image'))
shadow.appendChild(content)
}
}
customElements.define("user-card", UserCard)
而在 HTML 中将会更加灵活使用此元素:
<user-card image="your-card.png">
<ul slot='name'>
<li>Tom</li>
<li>Peter</li>
</ul>
<p slot='email'>xi@qq.com</p>
</user-card>