习惯了使用vue
与react
等框架来开发组件, 但其实咱们能够不依赖任何框架, 直接原生开发组件, 因此这个原生api
的一大优势就是能够不依赖任何的框架。css
浏览器自己支持组件是大趋势, 可是目前使用起来并不够好, 但这并不能阻挡咱们学习的脚步与对知识的好奇心, 并且我也相信原生组件几年后会成为一种主流的组件编写方式, 如今就让咱们一块儿来学习它吧。html
MDN上显示:前端
不建议直接上生产环境。vue
还记得是我第一次用qiankun.js
框架的时候看到的这个概念(接下来的文章会写微前端
相关实战), 这个技术能够实现一部分的css样式隔离
, 之因此说只是实现一部分样式隔离, 学完这篇文章你就懂了。html5
咱们新建一个html5
页面, 写上以下结构react
<!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>
奇怪的一幕出现了, 内部元素不可见而且在查看结构的控制台里出现了特殊的结构定义。web
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标签
插入进来效果也是同样的。效果以下:api
styles.textContent = ` * { color:red } body { background-color: red; } `
这里咱们在影子元素内部改变了body
的样式, 而这个样式没有做用到外面的body
身上。浏览器
经过上面操做你是否是感受这个沙盒能完美隔离css了? 那咱们如今对最外层的style
标签里面加上字体大小的样式, 由于影子元素
没法隔离可继承的样式。app
* { 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>
下图是我作的一个原生组件
, 而且附上了使用方法。
<cc-mw name="大魔王1" image="../imgs/利姆露.webp"/></cc-mw> <cc-mw name="大魔王2" image="../imgs/利姆露.webp"></cc-mw>
上面组件的使用看起来与vue
等框架里面的组件差很少, 可是它但是很娇气
的!
<cc-mw>
不能写成<ccMw>
若是以下方式书写去掉结尾闭合标签, 只会显示第一个, 第二个没有被渲染(这个真的好奇怪), 第二个组件
会默认被插到第一个组件
中, 因为被插入影子元素
因此不显示了。
<cc-mw name="大魔王1" image="../imgs/利姆露.webp"/> <cc-mw name="大魔王2" image="../imgs/利姆露.webp"/>
奇奇怪怪的现象不是此次的主题, 咱们继续研究干货。
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
, 放心书写吧与外面写法同样。
<template id="ccmw">
这句是让写咱们能够找到这个模板。
<style>
标签 咱们能够当成template
标签内部就是一个影子元素
的结构内部, 因此这里能够插入样式标签, 并不用js
协助。
:host
选择包含使用这段 CSS 的Shadow DOM
的影子宿主, 也就是组件的外壳父元素。
编写一个组件固然须要逻辑
代码啦, 该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>
上面的代码有个问题, 就是怎么组件代码
与业务代码放在了一块儿, 固然咱们能够经过技巧把他们拆散
, 这里使用的是模板字符串
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);
不能修改数据怎么能叫组件那, 这里咱们要利用类的方法。
组件类
添加方法: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>
其余的修改方法其实就一模一样了。
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>
效果以下:
这门技术可能暂时不必太深研究, 可是学会这门知识可使咱们有更广阔的技术视野, 不断学习老是会有用的, 此次就是这样, 但愿和你一块儿进步。