原生Web Components實(shí)現(xiàn)類Element UI中的Card卡片組件

Web Components 是一個(gè)瀏覽器原生支持的組件化方案叛赚,允許你創(chuàng)建新的自定義夹厌、可封裝、可重用的HTML 標(biāo)記责蝠。不用加載任何外部模塊党巾,直接就可以在瀏覽器中跑。本文就簡單介紹一下:使用 Web Components 實(shí)現(xiàn)一個(gè)類 Element UI 中的 Card 卡片組件玛歌。

Web Components && Card

一昧港、前言

隨著前端工程化生態(tài)日益成熟,出現(xiàn)了很多優(yōu)秀的框架支子,如:Vue创肥、ReactAngular等等值朋,極大的提高了日常開發(fā)效率叹侄。
其中組件化開發(fā)發(fā)揮了至關(guān)重要的作用,但是這些組件化開發(fā)都需要依賴第三方框架昨登,編譯打包之后才能在瀏覽器正常使用趾代。
而原生組件 Web Components ,相比與第三方框架使用起來更簡單直接丰辣,符合直覺撒强,不用加載任何外部模塊,代碼量小笙什。

二飘哨、Web Components 核心組成

  1. 自定義元素(custom element),使用 window.customElements.define API注冊(cè)
  2. Shadow DOM隔離琐凭,影藏標(biāo)記結(jié)構(gòu)芽隆、樣式和行為
  3. 可以在<template>中定義標(biāo)記結(jié)構(gòu)、樣式,多次重用胚吁。利用 slot 插槽牙躺、命名插槽,可以傳入定制化的結(jié)構(gòu)UI腕扶,使用上類似 Vue 中的 slot 插槽

1. Custom Elements

自定義的 HTML 標(biāo)簽孽拷,稱為自定義元素(custom element)。根據(jù)規(guī)范蕉毯,自定義元素的名稱必須包含連詞線-乓搬,用與區(qū)別原生的 HTML 元素思犁。所以代虾,<com-card>不能寫成<comcard>

<div id="custom-card" class="com-card">
  <div class="com-card-head">
    <slot name="head"></slot>
  </div>
  <div class="com-card-body">
    <slot></slot>
    <div class="link-wrap">
      <a class="link" href="" title=""></a>
    </div>
  </div>
</div>

<script>
  class ComCard extends HTMLElement {
    constructor() {
      super()
      var tplEle = document.getElementById('custom-card')
      this.append(tplEle)
    }
  }
  window.customElements.define('com-card', ComCard)
</script>

這樣就注冊(cè)了瀏覽器可識(shí)別渲染的一個(gè)自定義元素標(biāo)簽激蹲。

2. Shadow DOM

Shadow DOM 是對(duì)DOM的一個(gè)封裝棉磨。可以將標(biāo)記結(jié)構(gòu)学辱、樣式和行為隱藏起來乘瓤,并與頁面上的其他代碼相隔離,保證不同的部分不會(huì)混在一起策泣,可使代碼更加干凈衙傀、整潔。
使用自定義元素的 this.attachShadow() 方法可以開啟 Shadow DOM萨咕。

class ComCard extends HTMLElement {
  constructor() {
    super()
    var shadow = this.attachShadow({mode: 'closed'})  // open
    var tplEle = document.getElementById('custom-card')
    shadow.appendChild(tplEle)
  }
}
window.customElements.define('com-card', ComCard); 

其中參數(shù){ mode: 'closed' }统抬,表示 Shadow DOM 是封閉的,不允許外部訪問危队。

3. templates 和 slots

因?yàn)榻M件的樣式應(yīng)該與代碼封裝在一起聪建,只對(duì)自定義元素生效,不影響外部的全局樣式茫陆。所以金麸,可以把樣式寫在<template>里面,這樣作為自定義元素結(jié)構(gòu)的基礎(chǔ)可以被多次重用簿盅。

<template id="custom-card-template">
  <style>
    .com-card {
      
    }
  </style>
  <div class="com-card">
    
  </div>
</template>

<script>

  class ComCard extends HTMLElement {
    constructor() {
      super();
      var shadow = this.attachShadow({mode: 'closed'})  // open
      var tplEle = document.getElementById('custom-card-template')
      var content = tplEle.content.cloneNode(true)

      shadow.appendChild(content)
    }
  }

  window.customElements.define('com-card', ComCard);
</script>

三挥下、完整代碼

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Web Component</title>
  <style>
    * {
        box-sizing: border-box;
    }
    body {
        font-size: 14px;
    }
    .box {
        padding: 5px 0 30px;
    }
    .box .caption {
        display: none;
    }
    .box h1 {
        text-align: center;
    }
    .box li {
        color: #666;
        font-size: 14px;
        line-height: 1.8;
        margin-top: 15px;
    }
    .img {
        display: block;
        width: 80%;
        margin: 0 !important;
    }
    .card-head {
        display: flex;
        justify-content: space-between;
        align-items: center;
    }
    .card-title {
        color: #333;
        font-size: 16px;
    }
    .card-head-btn {
        color: #409eff;
        cursor: pointer;
        text-decoration: none !important;
    }
    .card-head-btn:hover {
        text-decoration: none;
    }
  </style>
</head>
<body>

<div class="box">

  <h1>Web Component</h1>

  <com-card data-show-head="0" data-url="https://tiven.cn" data-title="天問博客">
    <div slot="head" class="card-head">
      <div class="card-title">卡片名稱</div>
      <a class="card-head-btn">操作按鈕</a>
    </div>
    <img class="img" src="https://tiven.cn/static/img/kpl-sunwukong-a3Lt-ed2NG9r4NFDm_9DA.jpg" alt="天問">
  </com-card>

  <br>
  <br>

  <com-card data-show-head="1" data-url="https://tiven.cn/p/de241e23/" data-title="Vite+Vue3+Vant快速構(gòu)建項(xiàng)目">
    <div slot="head" class="card-head">
      <div class="card-title">卡片名稱</div>
      <a class="card-head-btn" onclick="hello()">操作按鈕</a>
    </div>
    <img class="img" src="https://tiven.cn/static/img/kpl-xuance-JqX71qH7aTflHV_gqvhIc.jpg" alt="天問">
    <ol>
      <li>君不見黃河之水天上來,奔流到海不復(fù)回桨醋。</li>
      <li>君不見高堂明鏡悲白發(fā)棚瘟,朝如青絲暮成雪。</li>
      <li>天生我材必有用讨盒,千金散盡還復(fù)來解取。</li>
    </ol>
  </com-card>

</div>

<template id="custom-card-template">
  <style>
    .com-card {
        min-width: 200px;
        min-height: 100px;
        border-radius: 4px;
        border: 1px solid #ebeef5;
        background-color: #fff;
        overflow: hidden;
        color: #303133;
        transition: .3s;
        box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
    }
    .com-card-head {
        padding: 10px 20px;
        border-bottom: 1px solid #ebeef5;
        box-sizing: border-box;
    }
    .com-card-body {
        padding: 20px;
    }
    .link-wrap {
        text-align: left;
        padding-top: 20px;
    }
    .link {
        display: inline-block;
        height: 42px;
        line-height: 43px;
        padding: 0 30px;
        text-align: center;
        cursor: pointer;
        color: #fff;
        background-color: #409eff;
        border-color: #409eff;
        -webkit-appearance: none;
        box-sizing: border-box;
        outline: none;
        transition: .1s;
        font-weight: 500;
        -moz-user-select: none;
        -webkit-user-select: none;
        -ms-user-select: none;
        font-size: 14px;
        border-radius: 4px;
        text-decoration: none !important;
    }
  </style>
  <div class="com-card">
    <div class="com-card-head">
      <slot name="head"></slot>
    </div>
    <div class="com-card-body">
      <slot></slot>
      <div class="link-wrap">
        <a class="link" href="" title=""></a>
      </div>
    </div>
  </div>
</template>

<script>

  class ComCard extends HTMLElement {
    constructor() {
      super();
      var shadow = this.attachShadow({mode: 'closed'})  // open
      var tplEle = document.getElementById('custom-card-template')
      var content = tplEle.content.cloneNode(true)

      var attrList = Array.from(this.attributes);
      var props = attrList.reduce((prev, item)=>{
        prev[item.name] = item.value
        return prev
      }, {})

      if (props['data-show-head']!=='1') {
        var head = content.querySelector('.com-card-head')
        head.remove()
      }

      var urlEle = content.querySelector('.link')
      if (props['data-url'] && props['data-title']) {
        urlEle.href = props['data-url']
        urlEle.title = props['data-title']
        urlEle.innerText = props['data-title']
      } else {
        urlEle.remove()
      }

      shadow.appendChild(content)
    }
    connectedCallback(){
      //在這里發(fā)送數(shù)據(jù)請(qǐng)求(Ajax)
      console.log('connectedCallback')
    }
    //被從文檔DOM中刪除時(shí)調(diào)用
    disconnectedCallback(){
      console.log('disconnectedCallback')
    }
    //被移動(dòng)到新的文檔時(shí)調(diào)用
    adoptedCallback(){
      console.log('adoptedCallback')
    }
    //當(dāng)增加、刪除返顺、修改自身的屬性時(shí)被調(diào)用
    attributeChangedCallback(){
      console.log('attributeChangedCallback')
    }
    
  }

  window.customElements.define('com-card', ComCard);

  function hello() {
    alert('Hello禀苦,Web Component')
  }
</script>
</body>
</html>

最終效果如上圖所示蔓肯,具體demo演示地址:https://tiven.cn/demo/web-component.html

四、Web Components vs Vue Components

Vue Component Web Component
data 實(shí)例屬性
props attributes
watch observedAttributes振乏、attributeChangedCallback
computed getters
methods class methods
mounted connectedCallback
destroyed disconnectedCallback
style scoped template中的style
template template

五蔗包、Web Components 生命周期回調(diào)函數(shù)

  • connectedCallback:當(dāng) custom element首次被插入文檔DOM時(shí),被調(diào)用慧邮。
  • disconnectedCallback:當(dāng) custom element從文檔DOM中刪除時(shí)调限,被調(diào)用。
  • adoptedCallback:當(dāng) custom element被移動(dòng)到新的文檔時(shí)误澳,被調(diào)用耻矮。
  • attributeChangedCallback: 當(dāng) custom element增加、刪除忆谓、修改自身屬性時(shí)裆装,被調(diào)用。

六倡缠、優(yōu)點(diǎn) and 缺點(diǎn)

優(yōu)點(diǎn):

  1. 瀏覽器原生支持哨免,不需要引入額外的第三方庫
  2. 語義化
  3. 復(fù)用性,移植性高
  4. 不同團(tuán)隊(duì)不同項(xiàng)目可以共用組件

缺點(diǎn):

  1. 需要操作DOM
  2. 目前瀏覽器兼容性昙沦、性能方面不夠友好
  3. 和外部css交互比較難

七琢唾、基于web components的框架

  • LitElement 是一個(gè)快速、輕量級(jí)的 Web UI 框架盾饮。使用 lit-html 來渲染元素采桃。
  • Polymer 是一款實(shí)用、基于事件驅(qū)動(dòng)丐谋、封裝性和交互性強(qiáng)的 Web UI 框架芍碧。
  • Omi 是基于 Web 組件的跨框架跨平臺(tái)框架 。移動(dòng)端 & 桌面 & 小程序号俐。

歡迎訪問:天問博客

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末泌豆,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子吏饿,更是在濱河造成了極大的恐慌踪危,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,816評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件猪落,死亡現(xiàn)場離奇詭異贞远,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)笨忌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門蓝仲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事袱结×料叮” “怎么了?”我有些...
    開封第一講書人閱讀 158,300評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵垢夹,是天一觀的道長溢吻。 經(jīng)常有香客問我,道長果元,這世上最難降的妖魔是什么促王? 我笑而不...
    開封第一講書人閱讀 56,780評(píng)論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮而晒,結(jié)果婚禮上蝇狼,老公的妹妹穿的比我還像新娘。我一直安慰自己欣硼,他們只是感情好题翰,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評(píng)論 6 385
  • 文/花漫 我一把揭開白布恶阴。 她就那樣靜靜地躺著诈胜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪冯事。 梳的紋絲不亂的頭發(fā)上焦匈,一...
    開封第一講書人閱讀 50,084評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音昵仅,去河邊找鬼缓熟。 笑死,一個(gè)胖子當(dāng)著我的面吹牛摔笤,可吹牛的內(nèi)容都是我干的够滑。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼吕世,長吁一口氣:“原來是場噩夢啊……” “哼彰触!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起命辖,我...
    開封第一講書人閱讀 37,912評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤况毅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后尔艇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體尔许,經(jīng)...
    沈念sama閱讀 44,355評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評(píng)論 2 327
  • 正文 我和宋清朗相戀三年终娃,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了味廊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,809評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖余佛,靈堂內(nèi)的尸體忽然破棺而出迅皇,到底是詐尸還是另有隱情,我是刑警寧澤衙熔,帶...
    沈念sama閱讀 34,504評(píng)論 4 334
  • 正文 年R本政府宣布登颓,位于F島的核電站,受9級(jí)特大地震影響红氯,放射性物質(zhì)發(fā)生泄漏框咙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評(píng)論 3 317
  • 文/蒙蒙 一痢甘、第九天 我趴在偏房一處隱蔽的房頂上張望喇嘱。 院中可真熱鬧,春花似錦塞栅、人聲如沸者铜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽作烟。三九已至,卻和暖如春砾医,著一層夾襖步出監(jiān)牢的瞬間拿撩,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評(píng)論 1 267
  • 我被黑心中介騙來泰國打工如蚜, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留压恒,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,628評(píng)論 2 362
  • 正文 我出身青樓错邦,卻偏偏與公主長得像探赫,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子撬呢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容

  • 現(xiàn)在的前端開發(fā)基本離不開 React伦吠、Vue 這兩個(gè)框架的支撐,而這兩個(gè)框架下面又衍生出了許多定義組件庫: 這些組...
    wan_c1121閱讀 1,080評(píng)論 1 2
  • 前言:這周完成了兩場技術(shù)分享會(huì)倾芝,下周還有一場讨勤,就完成了這階段的一個(gè)重大任務(wù)。分享會(huì)是關(guān)于 TS 的晨另,我這兩場分享會(huì)...
    CondorHero閱讀 980評(píng)論 0 2
  • 組件的概念 組件潭千,是數(shù)據(jù)和方法的一個(gè)封裝,其定義了一個(gè)可重用的軟件元素的功能借尿,展示和使用刨晴,通常表現(xiàn)為一個(gè)或一組可重...
    zx_lau閱讀 2,433評(píng)論 0 3
  • 前言 不知不覺屉来,2019年即將接近尾聲,現(xiàn)有前端三大框架也各自建立著自己的生態(tài)狈癞、自己的使用群體茄靠。從angular1...
    Kaku_fe閱讀 2,761評(píng)論 0 19
  • 定義 瀏覽器原生支持的一套組件封裝技術(shù)。這里有兩個(gè)關(guān)鍵字: 組件技術(shù) 瀏覽器原生 組件化一直是前端完善的方向蝶桶,從早...
    cd2001cjm閱讀 696評(píng)論 0 0