Web Components 入门实例教程
转自阮一峰http://www.ruanyifeng.com/blog/2019/08/web_components.html
组件是前端的发展方向,现在流行的 React 和 Vue 都是组件框架。
谷歌公司由于掌握了 Chrome 浏览器,一直在推动浏览器的原生组件,即 Web Components API。相比第三方框架,原生组件简单直接,符合直觉,不用加载任何外部模块,代码量小。目前,它还在不断发展,但已经可用于生产环境。
Web Components API 内容很多,本文不是全面的教程,只是一个简单演示,让大家看一下怎么用它开发组件。

一、自定义元素
下图是一个用户卡片。

本文演示如何把这个卡片,写成 Web Components 组件,这里是最后的完整代码。
网页只要插入下面的代码,就会显示用户卡片。
<user-card></user-card>
这种自定义的 HTML 标签,称为自定义元素(custom element)。根据规范,自定义元素的名称必须包含连词线,用与区别原生的 HTML 元素。所以,<user-card>不能写成<usercard>。
二、customElements.define()
自定义元素需要使用 JavaScript 定义一个类,所有<user-card>都会是这个类的实例。
class UserCard extends HTMLElement {
constructor() {
super();
}
}
上面代码中,UserCard就是自定义元素的类。注意,这个类的父类是HTMLElement,因此继承了 HTML 元素的特性。
接着,使用浏览器原生的customElements.define()方法,告诉浏览器<user-card>元素与这个类关联。
window.customElements.define('user-card', UserCard);
三、自定义元素的内容
自定义元素<user-card>目前还是空的,下面在类里面给出这个元素的内容。
class UserCard extends HTMLElement {
constructor() {
super(); var image = document.createElement('img');
image.src = 'https://semantic-ui.com/images/avatar2/large/kristy.png';
image.classList.add('image'); var container = document.createElement('div');
container.classList.add('container'); var name = document.createElement('p');
name.classList.add('name');
name.innerText = 'User Name'; var email = document.createElement('p');
email.classList.add('email');
email.innerText = 'yourmail@some-email.com'; var button = document.createElement('button');
button.classList.add('button');
button.innerText = 'Follow'; container.append(name, email, button);
this.append(image, container);
}
}
上面代码最后一行,this.append()的this表示自定义元素实例。
完成这一步以后,自定义元素内部的 DOM 结构就已经生成了。
四、<template>标签
使用 JavaScript 写上一节的 DOM 结构很麻烦,Web Components API 提供了<template>标签,可以在它里面使用 HTML 定义 DOM。
<template id="userCardTemplate">
<img src="https://semantic-ui.com/images/avatar2/large/kristy.png" class="image">
<div class="container">
<p class="name">User Name</p>
<p class="email">yourmail@some-email.com</p>
<button class="button">Follow</button>
</div>
</template>
然后,改写一下自定义元素的类,为自定义元素加载<template>。
class UserCard extends HTMLElement {
constructor() {
super(); var templateElem = document.getElementById('userCardTemplate');
var content = templateElem.content.cloneNode(true);
this.appendChild(content);
}
}
上面代码中,获取<template>节点以后,克隆了它的所有子元素,这是因为可能有多个自定义元素的实例,这个模板还要留给其他实例使用,所以不能直接移动它的子元素。
到这一步为止,完整的代码如下。
<body>
<user-card></user-card>
<template>...</template> <script>
class UserCard extends HTMLElement {
constructor() {
super(); var templateElem = document.getElementById('userCardTemplate');
var content = templateElem.content.cloneNode(true);
this.appendChild(content);
}
}
window.customElements.define('user-card', UserCard);
</script>
</body>
五、添加样式
自定义元素还没有样式,可以给它指定全局样式,比如下面这样。
user-card {
/* ... */
}
但是,组件的样式应该与代码封装在一起,只对自定义元素生效,不影响外部的全局样式。所以,可以把样式写在<template>里面。
<template id="userCardTemplate">
<style>
:host {
display: flex;
align-items: center;
width: 450px;
height: 180px;
background-color: #d4d4d4;
border: 1px solid #d5d5d5;
box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1);
border-radius: 3px;
overflow: hidden;
padding: 10px;
box-sizing: border-box;
font-family: 'Poppins', sans-serif;
}
.image {
flex: 0 0 auto;
width: 160px;
height: 160px;
vertical-align: middle;
border-radius: 5px;
}
.container {
box-sizing: border-box;
padding: 20px;
height: 160px;
}
.container > .name {
font-size: 20px;
font-weight: 600;
line-height: 1;
margin: 0;
margin-bottom: 5px;
}
.container > .email {
font-size: 12px;
opacity: 0.75;
line-height: 1;
margin: 0;
margin-bottom: 15px;
}
.container > .button {
padding: 10px 25px;
font-size: 12px;
border-radius: 5px;
text-transform: uppercase;
}
</style> <img src="https://semantic-ui.com/images/avatar2/large/kristy.png" class="image">
<div class="container">
<p class="name">User Name</p>
<p class="email">yourmail@some-email.com</p>
<button class="button">Follow</button>
</div>
</template>
上面代码中,<template>样式里面的:host伪类,指代自定义元素本身。
六、自定义元素的参数
<user-card>内容现在是在<template>里面设定的,为了方便使用,把它改成参数。
<user-card
image="https://semantic-ui.com/images/avatar2/large/kristy.png"
name="User Name"
email="yourmail@some-email.com"
></user-card>
<template>代码也相应改造。
<template id="userCardTemplate">
<style>...</style> <img class="image">
<div class="container">
<p class="name"></p>
<p class="email"></p>
<button class="button">Follow John</button>
</div>
</template>
最后,改一下类的代码,把参数加到自定义元素里面。
class UserCard extends HTMLElement {
constructor() {
super(); var templateElem = document.getElementById('userCardTemplate');
var 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');
this.appendChild(content);
}
}
window.customElements.define('user-card', UserCard);
七、Shadow DOM
我们不希望用户能够看到<user-card>的内部代码,Web Component 允许内部代码隐藏起来,这叫做 Shadow DOM,即这部分 DOM 默认与外部 DOM 隔离,内部任何代码都无法影响外部。
自定义元素的this.attachShadow()方法开启 Shadow DOM,详见下面的代码。
class UserCard extends HTMLElement {
constructor() {
super();
var shadow = this.attachShadow( { mode: 'closed' } ); var templateElem = document.getElementById('userCardTemplate');
var 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);
}
}
window.customElements.define('user-card', UserCard);
上面代码中,this.attachShadow()方法的参数{ mode: 'closed' },表示 Shadow DOM 是封闭的,不允许外部访问。
至此,这个 Web Component 组件就完成了,完整代码可以访问这里。可以看到,整个过程还是很简单的,不像第三方框架那样有复杂的 API。
八、组件的扩展
在前面的基础上,可以对组件进行扩展。
(1)与用户互动
用户卡片是一个静态组件,如果要与用户互动,也很简单,就是在类里面监听各种事件。
this.$button = shadow.querySelector('button');
this.$button.addEventListener('click', () => {
// do something
});
(2)组件的封装
上面的例子中,<template>与网页代码放在一起,其实可以用脚本把<template>注入网页。这样的话,JavaScript 脚本跟<template>就能封装成一个 JS 文件,成为独立的组件文件。网页只要加载这个脚本,就能使用<user-card>组件。
这里就不展开了,更多 Web Components 的高级用法,可以接着学习下面两篇文章。
九、参考链接
- The anatomy of Web Components, Uday Hiwarale
(完)
Web Components 入门实例教程的更多相关文章
- React 入门实例教程(转载)
本人转载自: React 入门实例教程
- Omnet++ 4.0 入门实例教程
http://blog.sina.com.cn/s/blog_8a2bb17d01018npf.html 在网上找到的一个讲解omnet++的实例, 是4.0下面实现的. 我在4.2上试了试,可以用. ...
- let import export React入门实例教程 connect provider combineReducers 箭头函数 30分钟掌握ES6/ES2015核心内容 Rest babel
let与var的区别 http://www.cnblogs.com/snandy/archive/2015/05/10/4485832.html es6 导入导出 http://www.csdn.ne ...
- WebService入门实例教程
什么是WebService 通过使用WebService,您的应用程序可以向全世界发布信息,或提供某项功能,它是基于Web的服务,通过Web进行发布.查找和使用. WebService脚本平台需支持X ...
- React 入门实例教程
现在最热门的前端框架,毫无疑问是 React . 上周,基于 React 的 React Native 发布,结果一天之内,就获得了 5000 颗星,受瞩目程度可见一斑. React 起源于 Face ...
- Facebook React.js库 入门实例教程
作者: 阮一峰 日期: 2015年3月31日 现在最热门的前端框架,毫无疑问是 React . 上周,基于 React 的 React Native 发布,结果一天之内,就获得了 5000 颗星,受瞩 ...
- 2015年最热门前端框架React 入门实例教程
现在最热门的前端框架,毫无疑问是 React . 上周,基于 React 的 React Native 发布,结果一天之内,就获得了 5000 颗星,受瞩目程度可见一斑. React 起源于 Face ...
- React入门实例教程
文章转自:阮一峰 现在最热门的前端框架,毫无疑问是 React . 上周,基于 React 的 React Native 发布,结果一天之内,就获得了 5000 颗星,受瞩目程度可见一斑. React ...
- React 入门实例教程(转载)
现在最热门的前端框架,毫无疑问是 React . 上周,基于 React 的 React Native 发布,结果一天之内,就获得了 5000 颗星,受瞩目程度可见一斑. React 起源于 Face ...
随机推荐
- 图解再谈ssh port forwarding-ssh隧道技术
https://www.ramkitech.com/2012/04/how-to-do-ssh-tunneling-port-forwarding.html https://www.cnblogs.c ...
- div不换行的三种方法
原文:https://www.cnblogs.com/zouwangblog/p/11149621.html float <div class="div1">123&l ...
- xcode11新项目删除main.storyboard 两种方法
方法一 心急的童鞋按照老操作完成后再按照如下操作即可 /** 弃用storboard 1.info.plist去除 <key>UIApplicationSceneManifest</ ...
- 下载安装Tomcat教程
注:由于我的笔记不知怎么滴不能复制粘贴我就直接贴图了
- js 对象克隆方法总结(不改变原对象)
1.通用对象克隆: function clone(obj){ let temp = null; if(obj instanceof Array){ temp = obj.concat(); }else ...
- 从 Vue 的视角学 React(二)—— 基本语法
基于 Vue.js 开发的时候,每个 vue 文件都是一个单独的组件,可以包含 HTML,JS,CSS 而 React 是以函数为基础,每个 function 就是一个组件.虽然 JSX 让 HTML ...
- Python 常用语句
条件语句 a=input("请输入数字a的值:\n") a=int(a) #从控制台接收到的都是字符串类型,需要转换 if a==0: #也可以写成if(a==0): print( ...
- 关于SQL中的 where 1 = 1 的用法
在项目中的常见的一个操作:在有关SQL的代码中加入where 1 = 1,关于它的用法,可以总结如下: 首先,where 1 = 1的用法往往是为了方便后续的给SQL增加where限制条件.如果实现加 ...
- MongoDB分片,唯一索引与upsert
前言 分片,唯一索引和upsert,表面上看似没有直接联系的几个东西,到底存在怎样的瓜葛呢? 分片 为了保持水平扩展的有效性,分片功能必须保证各个片之间没有直接关联,不需要与其他分片交互就可以独立做出 ...
- 运维基础——Zabbix:Lack of free swap space on Zabbix server
问题 使用Zabbix监控一些云主机时,可能遇到: Lack of free swap space on Zabbix server 使用命令: free -m 看到: Swap 的total,use ...