自定义web组件学习
参考文档 https://developers.google.com/web/fundamentals/web-components/customelements
http://www.ruanyifeng.com/blog/2019/08/web_components.html
测试环境 Chrome版本 88.0.4324.190 64位 / win10系统
自定义web组件是js api提供的新能力,感觉是个很有用的功能,但为什么要发明这个东西呢,自胡乱思量了一番.
早期的html网页
html超文本标记语言提供了很多标记,<div> <span> <p> <img> <h1>等等,每个标记表达一个含义,这个是语义化.
<div>,<span>表示一个内熔块,<p>表示一个段落,<img>是一个图片,<h1>是一个标题,通过使用这些标记,可以将
一篇文章显示在网页上.合理的使用这些标记可以得到一个语义化的,排版设计对人友好的文章.
语义化的用处之一是可以让搜索引擎通过这些标记找到这片文章的关键词,便于在网上搜索到.
如果认为语义化没什么用处,只是要排版,那么全部使用一种标记也没问题,比如全部用<div>.
使用标记对这片文章进行排版格式化,其实就是让文章符合一个格式协议,这样就能在网上传播,被搜索引擎找到,被浏览器解析呈现给读者.
早期的html主要是文本为主,图片很少,声音和视频基本没有.
html有个天生的跨系统平台优势,html文本在浏览器里打开,只要是有浏览器的系统,都可以打开html.虽然各家系统的
浏览器都有一些自家特性,会导致烦人的兼容性问题.但是总体是在W3C规范的框架里的.现在浏览器是Chrome独大.
html功能增强
早期html功能比较少,除了文字图片,要加声音视频就比较麻烦,要使用flash插件,还有比如IE的active控件.这里有个问题
想一下,为什么非要到浏览器里搞这些东西呢,早期的html的能力不多,浏览器也不够强大,困难太多.
后来是逐渐认知到,在PC时代,软件是客户端软件占据主导,像听歌看视频这种要求在早期浏览器里比较难做的,客户端软件
是很容易的.后来网络时代,浏览器成了新的平台,就像PC时代的操作系统那种地位.
那么,以前人们在客户端软件做的事,就习惯在浏览器里做了.如果只要打开浏览器,就能做一堆软件做的事,是很方便的.
比如听歌,看电影,玩游戏,甚至PS作图.要达到这个目的,一个做法是把客户端软件的功能在浏览器里重新实现一遍.
比如,要在浏览器里实现看视频,客户端视频软件的界面有"开始"|"暂停"|"进度条"等等功能,早期是使用插件完成.插件其实是
一个客户端程序,要下载和安装,功能由插件实现,通过浏览器在页面显示出来.
一个网页的内容可以全部用插件实现,这种网页打开F12看会发现没几个HTML标记.可以认为是一个披着浏览器外衣的客户端程序,
优点是功能强大,缺点是插件要下载安装,会遇到很多安全提示.如果不想使用插件,就需要增强html/js的能力了.
自定义html标记
使用语义化的标记,例如<p>表示一个段落,也可以自定义标记,有别于<div><p>这些原生的标记,自定义标记的名字要有一个"-"号,
这个在参考文档里有详细说明.
做个测试,自定义一个标记 <c-tag>自定义标记 c-tag 1</c-tag>
1. 在浏览器里显示正常,没有默认样式
2. 加个样式也有效 <c-tag class="c-tag">自定义标记 c-tag 1</c-tag>
.c-tag { display: inline-block; padding: 20px; }
3. 查找元素也有,说明在Document文档树里
4.绑个事件也能触发
document.body.querySelector('c-tag').addEventListener('click', () => {console.log(111);});
这个测试说明自定义标记没有问题,浏览器支持的.做自定义标记主要的目的在于交互能力,通过JS交互.如果只是为了展示内容.
那么原生的<div><p><img>这些就够用了,自定义标记的作用意义不大.
比如H5的<video>标记,可以简单的实现播放视频.<video>的界面有基本的按钮,看视频的基础功能都实现了.这个就是有交互的.
自定义html关联js类
原生的html标记有个对应的js类,比如<div>的类是 HTMLDivElement , html标记有个基类对象 HTMLElement .其它标记从这个类继承.
自定义标记也可以有对应的js类对象,定义关联使用 window.customElements.define() 这个API.
// 先定义一个类,要从HTMLElement继承
class CTag extends HTMLElement {
constructor() {
// 必须首先调用 super 方法
super();
// 元素的功能代码写在这里
...
}
}
// 调用API,第一个参数是自定义标记的名字,命名规则要有-号,第二个参数就是上面的类
window.customElements.define('c-tag', CTag);
// 也可以一步写成,省去定义类思考类名字的麻烦,写成匿名类,做第二个参数
window.customElements.define('c-tag', class extends HTMLElement { ... });
自定义web组件
自定义标记,再定义这个标记的类,最后调用API将标记和类联系起来.在浏览器里注册了这个自定义标记.这就是个浏览器"自带"组件了
组件是模块化的体现,计算机就是模块化的,硬件软件都是.将特定功能的代码打包成一个模块,可以重复使用.可以给组件设置属性,调用方法,
绑定事件等.类似于使用winform开发windows桌面程序,可以拖组件迅速完成界面,也支持自定义组件.这个方式在浏览器里也可以有了.
给c-tag自定义组件加个无聊的功能
// 页面上写个组件,设置一个属性title
<c-tag title="自定义标记 c-tag 1"></c-tag>
class CTag extends HTMLElement { constructor(title) {
// 必须首先调用 super 方法 super();
// 元素的功能代码写在这里
// 1. 读取在标记上设置的title属性的值,然后设置到标记的innerText
this.innerText = this.getAttribute('title') || title;
// 2. 标记点击时,字体颜色变红色
this.addEventListener('click', () => { if (this.style.color) { this.style.color = ''; return; } this.style.color = 'red'; }) }
}
在constructor构造函数里,this就表示c-tag这个元素,this指代自定义元素本身.
// 元素每次插入到 DOM 时都会调用.用于运行安装代码,例如获取资源或渲染.一般来说,您应将工作延迟至合适时机执行
connectedCallback() {
}
经过实验,在构造函数里添加标记或者设置innertext会包错,可以在上面这个钩子函数里执行这些操作
组件的html结构绝大多数不可能只有个根元素,在自定义元素里加入后代元素,用this.append().
查询后代元素,可以用this.querySelectorAll()
// 在组件里加个p标记,下面这句话是写在构造函数里的.只需要在页面上写上标记<c-tag></c-tab>,浏览器解析呈现页面后,会执行构造函数
this.append(document.createElement('p'));
自定义组件使用JS方式新建对象
// customElements.get()方法,传入标记名字,返回这个标记关联的类的构造函数
const ctag = window.customElements.get('c-tag');
// 调用这个构造函数,建立对象
const ctag1 = new ctag("js添加");
// 加到dom里
document.body.append(ctag1);
建立自定义标记,建立对应的JS类,在类里定义各种JS函数实现交互功能,定义dom结构实现界面,再编写CSS样式实现呈现方式,这样就是个web组件.
Shadow DOM
参考文档 https://developer.mozilla.org/zh-CN/docs/Web/Web_Components/Using_shadow_DOM
这个功能是隐藏dom,它的作用没看明白.看了下<video>元素,发现这个功能可以隐藏组件的内部DOM元素.这些元素在使用时,外部不用关心,
这也是模块化的一个特点
<video></video>元素有这么多按钮,但是DOM却很简洁
个中原因查了下资料,发现video元素隐藏了这些按钮对应的dom元素.要在F12里看到这些元素,要打开这个选项
勾选后再看video,发现多了shadow-root这个节点,里面有很多元素,这些可能都是video元素的实现细节吧.
那么,这个功能隐藏了模块细节,这个思路对应于OOP的类就类似于,类内部的数据使用private修饰,对外不可见,体现封装.
自定义组件如果要使用这个功能,用这个API.
// attachShadow()这个方法意思是,以this为根元素,(this是自定义元素本身)建立一个隐藏dom. mode可以选open closed
let shadowRoot = this.attachShadow({ mode: 'open' });
// 如果向这个shadowdom加元素,那么这些加入的元素就是在"shadow-root"这个节点下的,也就是隐藏的
shadowRoot = `<p></p>`;
在浏览器里测试发现,就算不打开 show user agent shadow dom这个选项,自定义组件的 shadow-root节点也会显示出来,不像原生的video组件完全看不到shadow-root节点.
自定义组件和jquery插件
大名鼎鼎的jQuery库是一个js API的巧妙封装,原生的js使用比较麻烦,各浏览器还有兼容性问题.jQuery抽象出一套标准的dom操作的api非常便利,解决了兼容性问题.
就算没有学习过原生js,直接使用jQuery也可以完成诸如,选择dom,修改dom属性,位置,css样式,绑定事件,发ajax请求,这些基本操作.
原生js丰富后,有了像 querySelectorAll() fetch()这些api,也可以完成dom选择和ajax,便利程度和jQuery一样了.不知道是不是jQuery客观上促进了js的发展.
jQuery插件把用于界面UI的dom模板和交互的js函数功能绑定在一起,做成一个插件,实现特定功能.比如轮播图片插件,先写好dom结构,然后用调用插件API实例化,传入参数就可以用了,
这就是一个web组件.jQuery插件就是web开发组件化的早期实现.从实现功能的角度看,这个实现是很成功的.
后来组件化进一步发展,有react,vue这些组件框架.现在又有了自定义web组件API,终于也可以自定义浏览器"自带"的,html/js/css的web组件了.