習慣了使用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>
效果以下:
這門技術可能暫時不必太深研究, 可是學會這門知識可使咱們有更廣闊的技術視野, 不斷學習老是會有用的, 此次就是這樣, 但願和你一塊兒進步。