写的比较简单,还在学习中,欢迎大家指教。
知识点:
WebComponent
CustomElementRegistry - customElements 对象
该接口提供注册自定义元素和查询已注册元素的方法
customElements.define(name, constructor, options)
name:自定义元素名称,注意:自定义的元素名称必须是使用 - ,且不能包含大写字母
constructor:自定义元素构造器
options :控制元素如何定义. 目前有一个选项支持
extends:指定继承的已创建的元素. 被用于创建自定义元素
Shadow DOM
一组JavaScript API,用于将封装的“影子”DOM树附加到元素(与主文档DOM分开呈现)并控制其关联的功能。通过这种方式,您可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突
- 自定义元素的容器
- 组件作用域
Element.attachShadow(shadowRootInit)
shadowRootInit包含:
mode:一个指定Shadow DOM封装模式的字符串,可以是下列之一:
open:开放的封装模式
closed:关闭的封装模式
Shadow DOM
获取子元素
组件.childNodes
获取组件内部元素
组件.shadowRoot
当mode为open,则返回shadowRoot
当mode为closed,返回null
Component
插槽(SLOT)
浏览器默认情况只渲染shadowDOM,不渲染子元素
我们可以通过组件内部使用 <slot></slot> 来获取包含的子元素,并在组件内部适当的位置插入子元素
具名插槽
我们还可以在子元素上添加 slot属性并赋值
<div slot=”a”></div>
那么在组件内部就是使用 <slot name=”a”></slot>的方式来调用具名插槽了
Component
生命周期回调
定义在自定义元素的类定义中的特殊回调函数
connectedCallback:当自定义元素第一次被连接到文档DOM时被调用
disconnectedCallback:当自定义元素与文档DOM断开连接时被调用
Component
生命周期回调
adoptedCallback:当自定义元素被移动到新文档时被调用,比如移动到一个iframe窗口等
attributeChangedCallback:当自定义元素的一个属性被增加、移除或更改时被调用
- 需要配合observedAttributes属性来监听指定的属性
static get observedAttributes() {return [要监听的属性列表] }
自定义事件:
CustomEvent
new CustomEvent(typeArg, customEventInit)
typeArg:一个表示 event 名字的字符串
customEventInit:一个字典类型参数,有如下字段
detail:可选的默认值是 null 的任意类型数据,是一个与 event 相关的值(对象)
bubbles:一个布尔值,表示该事件能否冒泡,默认值为 false
cancelable:一个布尔值,表示该事件是否可以取消,默认值为 false
composed:一个布尔值,表示事件是否会在影子DOM根节点之外触发侦听器,默认值为 false
CustomEvent
HTMLElement.dispatchEvent(event)
event:通过new CustomEvent创建的事件
HTMLElement.addEventListener(event, callback, options)
event:事件名称
callback:事件回调
options:选项
代码:
//自定义一个组件的基类,自定义的html标签组件都需要继承HTMLElement父类
class component extends HTMLElement{
constructor(){
super();
this._data = {};
this._data.tabData = [];
this._data.index = 0;
};
/*
* begin 给自定义元素用setAttribute设置属性时
*必需要用static get observedAttributes -- 注册用setAttribute的属性
*和attributeChangedCallback配合使用--当使用setAttribute之后会自动调用该方法,
*在这个用getAttribute得到相应属性值
*
*/
static get observedAttributes() {
return ["tabData","index"];
};
attributeChangedCallback(){
try{
this._data.tabData = JSON.parse(this.getAttribute("tabData"));
this._data.index = this.getAttribute("index");
}catch(e){};
this.creatLis();
};
initData(){
//shadow不是<m-radio>中的子元素,它是一个容器,容器里的内容替换<m-radio>在html中的的
//位置,我是把shadow当作一个document来看的,相当于在元素中也有一个document
let shadow = this.attachShadow({
mode: "open"
});
};
//创建tab元素
createTabDom(){
/* 创建选项卡dom结构 */
//创建div
let container = document.createElement("div");
container.classList.add("container");
let fragment = document.createDocumentFragment();
//创建ul
let ul = document.createElement("ul");
ul.classList.add("tabUl");
fragment.appendChild(ul);
container.appendChild(fragment);
this.shadowRoot.appendChild(container);
this.creatLis();
};
}
//选项卡构造方法 this指向的<my-radio> dom元素
class MyTab extends component{
constructor(){
super();
this.initData();
this.htmlStyle();
this.createTabDom();
this.createTabEvent();
};
//style样式
htmlStyle(){
/* style样式 */
let style = document.createElement("style");
style.textContent = `ul{
margin:0;
padding:0;
}
ul,li{
list-style: none;
}
li,div{
padding:0;
}
ul{
float: left;
}
li{
padding: 10px 20px;
border: 1px solid #eeeeee;
float:left;
font-size: 14px;
cursor: pointer;
color: #666666;
}
li:hover{
color:#ffffff;
background: -webkit-linear-gradient(left,rgb(82, 211, 243),#eeeeee45,rgb(82, 211, 243));
}
.active{
color:#ffffff;
background: -webkit-linear-gradient(left,rgb(82, 211, 243),#eeeeee45,rgb(82, 211, 243));
}
.container{
overflow: auto;
}`;
this.shadowRoot.appendChild(style);
};
creatLis(){
//创建li
let tabData = this._data.tabData;
if(tabData.length > 0){
let lis = tabData.map((item,index) => {
if(this._data.index === index.toString()){
return `<li class="active" data-num="${index}">${item}</li>`
}else{
return `<li data-num="${index}">${item}</li>`
}
}).join("");
let ul = this.shadowRoot.querySelector(".tabUl");
ul.innerHTML = lis;
}
};
createTabEvent(){
let me = this;
/* 绑定事件 */
this.shadowRoot.addEventListener("click",function(e){
if(e.target.tagName.toUpperCase() == "LI"){
me._data.index = e.target.dataset.num;
me.setAttribute("index", me._data.index);
me.creatLis();
//创建一个自己定义对象
let changeEvent = new CustomEvent("change",{
detail:{
index: me._data.index
}
});
//触发自定义事件
me.dispatchEvent(changeEvent);
}
});
//this.shadowRoot.onclick this.shadowRoot不支持onclick这种绑定事件的方式(不支持dom0级的绑定方式)
/* this.shadowRoot.onclick = function(e){
console.log(e);
if(e.targe == 'li'){
console.log(e);
}
} */
}
};
customElements.define("my-tab",MyTab); //html中写<my-tab></my-tab>标签,自动会调用MyTab类。
<!--html中调用组件的方式-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<!--begin 调用了我封装的组件调用几次就会用几个组件显示-->
<my-tab></my-tab>
<my-tab tabData='["美国","伊朗"]' index="1"></my-tab>
<!--end调用了我封装的组件-->
<!-- <script src="./选项卡封装.js"></script> -->
<script src="./选项卡继承式封装.js"></script>
<script>
let city = ["北京","上海","成都","重庆"];
let tab = document.querySelector('my-tab');//或者第一个控件<my-tab></my-tab>
tab.setAttribute("tabData",JSON.stringify(city));
//动态的将数据传入组件中,用JSON的原因是setAttribute不接受复杂数据类型(对象,数组等)所以将这些转化成字符串,再在组件中用JSON转回来。
//tab.setAttribute("index",0);//动态的给自定义元素设置属性及属性值,必须在对应的类中加入attributeChangedCallback:当自定义元素的一个属性被增加、移除或更改时被调用
//- 需要配合observedAttributes属性来监听指定的属性
//static get observedAttributes() {return [要监听的属性列表] } 这两个方法
//监听自定义事件
tab.addEventListener("change",function(e){
console.log(e.detail.index);
})
</script>
</body>
</html>