带你走进 -> 影子元素(Shadow DOM)& 浏览器原生组件开发(Web Components API)
本篇介绍
习惯了应用 vue
与react
等框架来开发组件, 但其实咱们能够不依赖任何框架, 间接原生开发组件, 所以这个原生 api
的一大长处就是能够不依赖任何的框架。
浏览器自身反对组件是大趋势, 然而目前应用起来并不够好, 但这并不能阻挡咱们学习的脚步与对常识的好奇心, 而且我也置信原生组件几年后会成为一种支流的组件编写形式, 当初就让咱们一起来学习它吧。
1. 兼容性
Chrome 54 Safari 10.1 Firefox 63
MDN 上显示:
不倡议间接上生产环境。
2. 影子元素
还记得是我第一次用 qiankun.js
框架的时候看到的这个概念 (接下来的文章会写 微前端
相干实战), 这个技术能够实现一部分的css 款式隔离
, 之所以说只是实现一部分款式隔离, 学完这篇文章你就懂了。
第一步: 生成影子元素
咱们新建一个 html5
页面, 写上如下构造
<!DOCTYPE html>
<html lang="en">
<head>
<style>
#cc-shadow {
margin: auto;
border: 1px solid #ccc;
width: 200px;
height: 200px;
}
</style>
</head>
<body>
<div id="cc-shadow">
<span> 我是外部元素 </span>
</div>
<script>
const oShadow = document.getElementById("cc-shadow");
const shadow = oShadow.attachShadow({mode: 'open'});
</script>
</body>
</html>
奇怪的一幕呈现了, 外部元素不可见并且在查看构造的控制台里呈现了非凡的构造定义。
attachShadow
办法给指定的元素挂载一个Shadow DOM
。mode: open
示意能够通过页面内的 JavaScript 办法来获取 Shadow DOM。mode: open
针对是dom.shadowRoot
办法, 间接 getElementsByClassName 获取还是能够获取到的 ( 这条很重要, 有的文章都说错了)。- 与
mode: open
对应的是mode: close
。 - 留神: 不能够先开后关这种操作
第二步: 往里面注入元素
const link = document.createElement("a");
link.href = 'xxxxxxxxxxxx';
link.innerHTML = '点我跳转';
shadow`.appendChild(link);
- 留神这里应用的是
shadow
, 而不是dom
自身。
第三步: 往里面注入款式
const styles = document.createElement("style");
styles.textContent = `* {color:red} `
shadow.appendChild(styles);
- 通过下面能够看出, 创立了一个
style
标签插入了进去。 - 与此相似咱们能够创立一个
link 标签
插入进来成果也是一样的。
成果如下:
第四步: 款式隔离试验
styles.textContent = `
* {color:red}
body {background-color: red;}
`
这里咱们在影子元素外部扭转了 body
的款式, 而这个款式没有作用到里面的 body
身上。
第五步: 款式浸透试验
通过下面操作你是不是感觉这个沙盒能完满隔离 css 了? 那咱们当初对最外层的 style
标签外面加上字体大小的款式, 因为 影子元素
无奈隔离可继承的款式。
* {font-size: 40px;}
成果如下:
总结一下:
影子元素的确能够避免款式泄露到里面净化全局, 然而也没法防止被全局的款式浸透净化, 这里的浸透指的是可继承的款式, 比方你里面 style
用 id 获取影子外面的元素扭转 border
属性那就是 有效的
, 所以qiankun.js
临时无奈完满隔离款式, 比方想要扭转全局款式就须要靠 js
帮忙。
有了上述的常识储备, 就让咱们来迎接下一位配角 原生组件
。
残缺代码(来复制玩玩吧):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#cc-shadow {
margin: auto;
border: 1px solid #ccc;
width: 200px;
height: 200px;
}
* {font-size: 40px;}
</style>
</head>
<body>
<div id="cc-shadow">
<span> 我是外部元素 </span>
</div>
<script>
// 1: 生成影子元素
const oShadow = document.getElementById("cc-shadow");
const shadow = oShadow.attachShadow({mode: 'open'});
// 2: 注入元素
const link = document.createElement("a");
link.href = 'xxxxxxxxxxxx';
link.innerHTML = '点我跳转';
shadow.appendChild(link);
// 3: 输出款式
const styles = document.createElement("style");
styles.textContent = `
* {color:red}
body {background-color: red;}
`
// 4: 插入应用, 能够应用插入 link 的形式插入 css, 成果雷同
shadow.appendChild(styles);
</script>
</body>
</html>
3. 原生组件的应用
下图是我做的一个 原生组件
, 并且附上了应用办法。
<cc-mw name="大魔王 1" image="../imgs/ 利姆露.webp"/></cc-mw>
<cc-mw name="大魔王 2" image="../imgs/ 利姆露.webp"></cc-mw>
下面组件的应用看起来与 vue
等框架外面的组件差不多, 然而它可是很 娇气
的!
注意事项
- 自定义元素的名称必须蕴含连词线,用与区别原生的 HTML 元素。所以,
<cc-mw>
不能写成<ccMw>
-
如果如下形式书写去掉结尾闭合标签, 只会显示第一个, 第二个没有被渲染 (这个真的好奇怪), 第二个
组件
会默认被插到第一个组件
中, 因为被插入影子元素
所以不显示了。<cc-mw name="大魔王 1" image="../imgs/ 利姆露.webp"/> <cc-mw name="大魔王 2" image="../imgs/ 利姆露.webp"/>
奇奇怪怪的景象不是这次的主题, 咱们持续钻研干货。
4. 编写组件第一步template
template
外面的 dom 构造就相当于 影子元素
的构造外部。
<template id="ccmw">
<style>
:host {
border: 1px solid red;
width: 200px;
margin-bottom: 10px;
display: block;
overflow: hidden;
}
.image {
width: 70px;
height: 70px;
}
.container {border: 1px solid blue;}
.container>.name {
font-size: 20px;
margin-bottom: 5px;
}
</style>
<img class="image">
<div class="container">
<p class="name"></p>
</div>
</template>
知识点逐个解释:
第一个: dom
定义
下面代码咱们拉倒最上面, 在这里咱们能够失常的定义dom
, 释怀书写吧与里面写法一样。
第二个: 定义 id
<template id="ccmw">
这句是让写咱们能够找到这个模板。
第三个: <style>
标签
咱们能够当成 template
标签外部就是一个 影子元素
的构造外部, 所以这里能够插入款式标签, 并不必 js
帮助。
第四个: :host
抉择蕴含应用这段 CSS 的 Shadow DOM
的影子宿主, 也就是组件的外壳父元素。
5. 组件类
编写一个组件当然须要 逻辑
代码啦, 该 js 闪亮出场了。
<script>
class CcMw extends HTMLElement {constructor() {super();
var shadow = this.attachShadow({mode: 'closed'});
var templateElem = document.getElementById('ccmw');
var content = templateElem.content.cloneNode(true);
content.querySelector('img').setAttribute('src', this.getAttribute('image'));
content.querySelector('.container>.name').innerText = this.getAttribute('name');
shadow.appendChild(content);
}
}
window.customElements.define('cc-mw', CcMw);
</script>
知识点逐个解释:
第一个: HTMLElement
截取 w3school
下面的定义, 由此可知这个父类赋予了组件 dom
元素的根底属性。
第二个: 老朋友attachShadow
把 dom
变成影子容器, 这样组件就能够独立进去了。
第三个: templateElem.content.cloneNode(true)
克隆出 模板里的元素
, 之所以是克隆因为组件会被复用。
第四个: window.customElements.define('cc-mw', CcMw);
组件名
与类名
互相绑定, 官网的话就是 该对象可用于注册新的自定义元素并获取无关以前注册的自定义元素的信息
。
第五个: 组件外部获取内部元素
组件内是能够获取大内部元素的, 所以能够对全局进行操作, 要慎用哦。
咱们甚至能够间接把组件插入到 body
中, 请留神容许, 但不提倡。
第六个: this
是谁
this
就是元素自身啦。
学完 影子元素
是不是就很轻松了解下面的操作都是在干嘛了, 开不开心。
附上残缺代码大家一起玩玩:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title> 原生组件 </title>
<style>
/* 不会影响外部的款式 */
.name {border: 2px solid red;}
</style>
</head>
<body>
<cc-mw name="大魔王 1" image="../imgs/ 利姆露.webp"/></cc-mw>
<cc-mw name="大魔王 2" image="../imgs/ 利姆露.webp"></cc-mw>
<template id="ccmw">
<style>
:host {
display: block;
overflow: hidden;
border: 1px solid red;
width: 200px;
margin-bottom: 10px;
}
.image {
width: 70px;
height: 70px;
}
.container {border: 1px solid blue;}
.container>.name {
font-size: 20px;
margin-bottom: 5px;
}
</style>
<img class="image">
<div class="container">
<p class="name"></p>
</div>
</template>
<script>
class CcMw extends HTMLElement {constructor() {super();
var shadow = this.attachShadow({mode: 'closed'});
var templateElem = document.getElementById('ccmw');
var content = templateElem.content.cloneNode(true);
content.querySelector('img').setAttribute('src', this.getAttribute('image'));
content.querySelector('.container>.name').innerText = this.getAttribute('name');
shadow.appendChild(content);
}
}
window.customElements.define('cc-mw', CcMw);
</script>
</body>
</html>
6. 集成为一个 js 文件
下面的代码有个问题, 就是怎么 组件代码
与业务代码放在了一起, 当然咱们能够通过技巧把他们 拆散
, 这里应用的是 模板字符串
js 生成模板动静插入。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title> 原生组件 </title>
<style>
/* 不会影响外部的款式 */
.name {border: 2px solid red;}
</style>
</head>
<body>
<cc-mw name="大魔王 1" image="../imgs/ 利姆露.webp"/></cc-mw>
<cc-mw name="大魔王 2" image="../imgs/ 利姆露.webp"></cc-mw>
<script src="./2. 拆散.js"></script>
</body>
</html>
下面代码清新了很多, 上面咱们能够分心写一个插件了:./2. 拆散.js
const template = document.createElement('template');
template.innerHTML = `
<style>
:host {
border: 2px solid red;
width: 200px;
margin-bottom: 10px;
display: block;
overflow: hidden;
}
.image {
width: 70px;
height: 70px;
}
.container {border: 1px solid blue;}
.container>.name {
font-size: 20px;
margin-bottom: 5px;
}
</style>
<img class="image">
<div class="container">
<p class="name"></p>
</div>
`
class CcMw extends HTMLElement {constructor() {super();
var shadow = this.attachShadow({mode: 'closed'});
var content = template.content.cloneNode(true);
content.querySelector('img').setAttribute('src', this.getAttribute('image'));
content.querySelector('.container>.name').innerText = this.getAttribute('name');
shadow.appendChild(content);
}
}
window.customElements.define('cc-mw', CcMw);
7. 动静批改数据
不能批改数据怎么能叫组件那, 这里咱们要利用类的办法。
为 组件类
增加办法:
class UserCard extends HTMLElement {constructor() {
// ...
this.oName = content.querySelector('.container>.name');
// ...
shadow.appendChild(content);
}
// 增加办法动静扭转 name
changeName(name){this.oName.innerText = name}
}
咱们在应用组件的页面应用如下代码:(留神: 这里为第一个组件加了id
)
<cc-mw name="大魔王 1" id="mw" image="../imgs/ 利姆露.webp"/></cc-mw>
<cc-mw name="大魔王 2" image="../imgs/ 利姆露.webp"></cc-mw>
<script src="./2. 拆散.js"></script>
<script>
const mw = document.getElementById('mw');
setTimeout(()=>{mw.changeName('批改后的魔王');
}, 1000)
</script>
其余的批改办法其实就一模一样了。
8. slot
插槽
在模板代码外面加上: (如果不传就显示默认文案)
<div class="container">
<p class="name"></p>
<slot name="msg"> 默认文案 </slot>
</div>
应用的时候:
<cc-mw name="大魔王 1" id="mw" image="../imgs/ 利姆露.webp"/>
<span slot="msg"> 进化了 </span>
</cc-mw>
<cc-mw name="大魔王 2" image="../imgs/ 利姆露.webp"></cc-mw>
成果如下:
end.
这门技术可能临时没必要太深钻研, 然而学会这门常识能够使咱们有更广大的技术视线, 一直学习总是会有用的, 这次就是这样, 心愿和你一起提高。