原文连接:https://ayushgp.github.io/html-web-components-using-vanilla-jscss
译者:阿里云 - 也树html
Web Component 出现有一阵子了。 Google 费了很大力气去推进它更普遍的应用,可是除 Opera 和 Chrome 之外的多数主流浏览器对它的支持仍然不够理想。html5
可是经过 polyfill,你能够从如今开始构建你本身的 Web Component,你能够在这里找到相关支持:www.webcomponents.org/polyfillsgit
在这篇文章中,我会演示如何建立带有样式,拥有交互功能而且在各自文件中优雅组织的 HTML 标签。github
Web Component 是一系列 web 平台的 API,它们能够容许你建立全新可定制、可重用而且封装的 HTML 标签,从而在普通网页及 web 应用中使用。web
定制的组件基于 Web Component 标准构建,能够在如今浏览器上使用,也能够和任意与 HTML 交互的 JavaScript 库和框架配合使用。ajax
用于支持 Web Component 的特性正逐渐加入 HTML 和 DOM 的规范,web 开发者使用封装好样式和定制行为的新元素来拓展 HTML 会变得垂手可得。npm
它赋予了仅仅使用纯粹的JS/HTML/CSS就能够建立可重用组件的能力。若是 HTML 不能知足需求,咱们能够建立一个能够知足需求的 Web Component。json
举个例子,你的用户数据和一个 ID 有关,你但愿有一个能够填入用户 ID 而且能够获取相应数据的组件。HTML 多是下面这个样子:浏览器
<user-card user-id="1"></user-card>
复制代码
这是一个 Web Component 最基本的应用。下面的教程将会聚焦在如何构建这个用户卡片组件。
HTML 和 DOM 标准定义了四种新的标准来帮助定义 Web Component。这些标准以下:
定制元素(Custom Elements): web 开发者能够经过定制元素建立新的 HTML 标签、加强已有的 HTML 标签或是二次开发其它开发者已经完成的组件。这个 API 是 Web Component 的基石。
HTML 模板(HTML Templates): HTML 模板定义了新的元素,描述一个基于 DOM 标准用于客户端模板的途径。模板容许你声明标记片断,它们能够被解析为 HTML。这些片断在页面开始加载时不会被用到,以后运行时会被实例化。
Shadow DOM: Shadow DOM 被设计为构建基于组件的应用的一个工具。它能够解决 web 开发的一些常见问题,好比容许你把组件的 DOM 和做用域隔离开,而且简化 CSS 等等。
HTML 引用(HTML Imports): HTML 模板(HTML Templates)容许你建立新的模板,一样的,HTML 引用(HTML imports)容许你从不一样的文件中引入这些模板。经过独立的HTML文件管理组件,能够帮助你更好的组织代码。
咱们首先须要声明一个类,定义元素如何表现。这个类须要继承 HTMLElement
类,但让咱们先绕过这部分,先来讨论定制元素的生命周期方法。你可使用下面的生命周期回调函数:
connectedCallback
— 每当元素插入 DOM 时被触发。
disconnectedCallback
— 每当元素从 DOM 中移除时被触发。
attributeChangedCallback
— 当元素上的属性被添加、移除、更新或取代时被触发。
在 UserCard
文件夹下建立 UserCard.js
:
class UserCard extends HTMLElement {
constructor() {
super();
this.addEventListener('click', e => {
this.toggleCard();
});
}
toggleCard() {
console.log("Element was clicked!");
}
}
customElements.define('user-card', UserCard);
复制代码
这个例子里咱们已经建立了一个定义了定制元素行为的类。customElements.define('user-card', UserCard);
函数调用告知 DOM 咱们已经建立了一个新的定制元素叫 user-card
,它的行为被 UserCard
类定义。如今能够在咱们的 HTML 里使用 user-card
元素了。
咱们会用到 https://jsonplaceholder.typicode.com/
的 API 来建立咱们的用户卡片。下面是数据的样例:
{
id: 1,
name: "Leanne Graham",
username: "Bret",
email: "Sincere@april.biz",
address: {
street: "Kulas Light",
suite: "Apt. 556",
city: "Gwenborough",
zipcode: "92998-3874",
geo: {
lat: "-37.3159",
lng: "81.1496"
}
},
phone: "1-770-736-8031 x56442",
website: "hildegard.org"
}
复制代码
如今,让咱们建立一个将在屏幕上渲染的模板。建立一个名为 UserCard.html
的新文件,内容以下:
<template id="user-card-template">
<div>
<h2>
<span></span> (
<span></span>)
</h2>
<p>Website: <a></a></p>
<div>
<p></p>
</div>
<button class="card__details-btn">More Details</button>
</div>
</template>
<script src="/UserCard/UserCard.js"></script>
复制代码
注意:咱们在类名前加了一个 card__
前缀。在较早版本的浏览器中,咱们不能使用 shadow DOM 来隔离组件 DOM。这样当咱们为组件编写样式时,能够避免意外的样式覆盖。
咱们建立好了卡片的模板,如今来用 CSS 装饰它。建立一个 UserCard.css
文件,内容以下:
.card__user-card-container {
text-align: center;
display: inline-block;
border-radius: 5px;
border: 1px solid grey;
font-family: Helvetica;
margin: 3px;
width: 30%;
}
.card__user-card-container:hover {
box-shadow: 3px 3px 3px;
}
.card__hidden-content {
display: none;
}
.card__details-btn {
background-color: #dedede;
padding: 6px;
margin-bottom: 8px;
}
复制代码
如今,在 UserCard.html
文件的最前面引入这个 CSS 文件:
<link rel="stylesheet" href="/UserCard/UserCard.css">
复制代码
样式已经就绪,接下来能够继续完善咱们组件的功能。
如今咱们须要定义建立元素而且添加到 DOM 中会发生什么。注意这里 constructor
和 connectedCallback
方法的区别。
constructor
方法是元素被实例化时调用,而 connectedCallback
方法是每次元素插入 DOM 时被调用。connectedCallback
方法在执行初始化代码时是颇有用的,好比获取数据或渲染。
小贴士: 在 UserCard.js
的顶部,定义一个常量 currentDocument
。它在被引入的 HTML 脚本中是必要的,容许这些脚本有途径操做引入模板的 DOM。像下面这样定义:
const currentDocument = document.currentScript.ownerDocument;
复制代码
接下来定义咱们的 connectedCallback
方法:
// 元素插入 DOM 时调用
connectedCallback() {
const shadowRoot = this.attachShadow({mode: 'open'});
// 选取模板而且克隆它。最终将克隆后的节点添加到 shadowDOM 的根节点。
// 当前文档须要被定义从而获取引入 HTML 的 DOM 权限。
const template = currentDocument.querySelector('#user-card-template');
const instance = template.content.cloneNode(true);
shadowRoot.appendChild(instance);
// 从元素中选取 user-id 属性
// 注意咱们要像这样指定卡片:
// <user-card user-id="1"></user-card>
const userId = this.getAttribute('user-id');
// 根据 user ID 获取数据,而且使用返回的数据渲染
fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
.then((response) => response.text())
.then((responseText) => {
this.render(JSON.parse(responseText));
})
.catch((error) => {
console.error(error);
});
}
复制代码
咱们已经定义好了 connectedCallback
方法,而且把克隆好的模板绑定到了 shadow root 上。如今咱们须要填充模板内容,而后在 fetch
方法获取数据后触发 render
方法。下面来编写 render
和 toggleCard
方法。
render(userData) {
// 使用操做 DOM 的 API 来填充卡片的不一样区域
// 组件的全部元素都存在于 shadow dom 中,因此咱们使用了 this.shadowRoot 这个属性来获取 DOM
// DOM 只能够在这个子树种被查找到
this.shadowRoot.querySelector('.card__full-name').innerHTML = userData.name;
this.shadowRoot.querySelector('.card__user-name').innerHTML = userData.username;
this.shadowRoot.querySelector('.card__website').innerHTML = userData.website;
this.shadowRoot.querySelector('.card__address').innerHTML = `<h4>Address</h4> ${userData.address.suite}, <br /> ${userData.address.street},<br /> ${userData.address.city},<br /> Zipcode: ${userData.address.zipcode}`
}
toggleCard() {
let elem = this.shadowRoot.querySelector('.card__hidden-content');
let btn = this.shadowRoot.querySelector('.card__details-btn');
btn.innerHTML = elem.style.display == 'none' ? 'Less Details' : 'More Details';
elem.style.display = elem.style.display == 'none' ? 'block' : 'none';
}
复制代码
既然组件已经完成,咱们就能够把它用在任意项目中了。为了继续教程,咱们须要建立一个 index.html
文件,而后写入下面的代码:
<html>
<head>
<title>Web Component</title>
</head>
<body>
<user-card user-id="1"></user-card>
<script src="https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/1.0.14/webcomponents-hi.js"></script>
<link rel="import" href="./UserCard/UserCard.html">
</body>
</html>
复制代码
由于并非全部浏览器都支持 Web Component,咱们须要引入 webcomponents.js 这个文件。注意咱们用到 HTML 引用语句来引入咱们的组件。
为了运行这些代码,你须要建立一个静态文件服务器。若是你不清楚如何建立,你可使用像 static-server
或者 json-server
这样的简易静态服务。教程里,咱们安装 static-server
:
$ npm install -g static-server
复制代码
接着在你的项目目录下,使用下面的命令运行服务器:
$ static-server
复制代码
打开你的浏览器并访问localhost:3000,你就能够看到咱们刚刚建立的组件了。
还有不少关于 Web Component 的东西没有在这篇短文中写到,我想简单的陈述一些开发 Web Component 的小贴士和技巧。
定制元素的名称必须包含一个短横线。因此 <my-tabs>
和 <my-amazing-website>
是合法的名称, 而<foo>
和 <foo_bar>
不行。
在 HTML 添加新标签时须要确保向前兼容,不能重复注册同一个标签。
定制元素标签不能是自闭合的,由于 HTML 只容许一部分元素能够自闭合。须要写成像 <app-drawer></app-drawer>
这样的闭合标签形式。
建立组件时可使用继承的方式。举个例子,若是想要为两种不一样的用户建立一个 UserCard
,你能够先建立一个基本的 UserCard 而后将它拓展为两种特定的用户卡片。想要了解更多组件继承的知识,能够查看Google web developers’ article。
咱们建立了当元素加入 DOM 后自动触发的 connectedCallback
方法。咱们一样有元素从 DOM 中移除时触发的 disconnectedCallback
方法。 attributesChangedCallback(attribute, oldval, newval)
方法会在咱们改变定制组件的属性时被触发。
既然组件元素是类的实例,就能够在这些类中定义公用方法。这些公用方法能够用来容许其它定制组件/脚原本和这些组件产生交互,而不是只能改变这些组件的属性。
能够经过多种方式定义私有方法。我倾向于使用(当即执行函数),由于它们易写和易理解。举个例子,若是你建立的组件有很是复杂的内部功能,你能够像下面这样作:
(function() {
// 使用第一个self参数来定义私有函数
// 当调用这些函数时,从类中传递参数
function _privateFunc(self, otherArgs) { ... }
// 如今函数只能够在你的类的做用域中可用
class MyComponent extends HTMLElement {
...
// 定义下面这样的函数可让你有途径和这个元素交互
doSomething() {
...
_privateFunc(this, args)
}
...
}
customElements.define('my-component', MyComponent);
})()
复制代码
为了防止新的属性被添加,须要冻结你的类。这样能够防止类的已有属性被移除,或者已有属性的可枚举、可配置或可写属性被改变,一样也能够防止原型被修改。你可使用下面的方法:
class MyComponent extends HTMLElement { ... }
const FrozenMyComponent = Object.freeze(MyComponent);
customElements.define('my-component', FrozenMyComponent);
复制代码
注意: 冻结类会阻止你在运行时添加补丁而且会让你的代码难以调试。
这篇关于 Web Component 的教程做用很是有限。这能够部分归咎于对 Web Component 的影响很大的 React。我但愿这篇文章能够提供给你足够的信息来让你尝试不添加任何依赖来构建本身的定制组件。你能够在 定制组件 API 规范(Custom components API spec) 找到更多关于 Web Component 的信息。
你能够在这里阅读第二部分的教程:使用纯粹的JS构建 Web Component - Part 2!