Web Components

最簡 Radio

最簡的自定義Radio,只是封裝了label:
封裝前:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <input type="radio" name="lan" checked  value="react" id="react">
    <label for="react">react</label> 
    <input type="radio" name="lan"   value="vue" id="vue">
    <label for="vue">vue</label> 

    <script>
        var $input =document.querySelector("input[name='lan'][checked]");
        console.log($input.checked);
    </script>
    </script>
</body>
</html>

封裝后:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>DC-RADIO</title>
    <script src="./dc-radio.js" defer></script>
  </head>
  <body>
    <dc-radio name="lan" value="react" disabled>react</dc-radio>
    <dc-radio name="lan" value="vue" checked>vue</dc-radio>
    <dc-radio name="lan" value="ng">ng</dc-radio>
    <button onclick="handleClick()">TEST</button>
    <script>
      function handleClick() {
        const $radio = document.querySelector("dc-radio[name='lan'][checked]");
        // $radio.checked = true;
        console.log($radio.value);
      }
    </script>
  </body>
</html>

Radio 實(shí)現(xiàn):

/**
 * dc-radio
 *
 * @description A radio button component
 * 
 * 1. name屬性相同的radio按鈕桶癣,會(huì)被認(rèn)為是一組经瓷,只能選擇一個(gè):添加事件監(jiān)聽change事件,其它Radio checked=false
 * (input checked -> radio checked)
 * (radio checked -> input checked)
 *
 */
class DCRadio extends HTMLElement {
    constructor() {
        super();
        this.attachShadow({ mode: 'open' });
        this.shadowRoot.innerHTML = `
            <style>
              
            </style>
            <label for="radio">
                <input type="radio" id="radio"/>
                <slot></slot>
            </label>      
        `
        this.$input = this.shadowRoot.querySelector('input')
    }

    get value() {
        return this.getAttribute('value') || this.textContent
    }

    get name() {
        return this.getAttribute('name')
    }

    set name(value) {
        this.setAttribute('name', value)
    }

    set disabled(value) {
        this.toggleAttribute('disabled', value)
    }

    set checked(value) {
        this.toggleAttribute('checked', value)

    }

    static get observedAttributes() {
        return ['checked', 'disabled']
    }

    connectedCallback() {
        this.$input.addEventListener('change', () => {
            this.checked = true
            this.dispatchEvent(new CustomEvent('change'))
        })
    }


    attributeChangedCallback(name, oldValue, newValue) {
        this.$input[name] = newValue !== null

        if (name === 'checked' && newValue !== null) {
            document.querySelectorAll(`dc-radio[name=${this.name}][checked]`).forEach(radio => {
                if (radio !== this) {
                    radio.checked = false
                }
            })
        }
    }
}

customElements.define('dc-radio', DCRadio);
  1. 定義自定義元素:通過繼承HTMLElement創(chuàng)建自定義元素,通過customElements.define()定義元素標(biāo)簽隘马;
  2. 定制元素內(nèi)容:通過shadowDom來承載元素內(nèi)容,包括樣式和結(jié)構(gòu)妻顶;
  3. 讀取屬性:由于屬性不是原生標(biāo)簽的屬性酸员,所以需要通過 getAttribute() 獲取屬性值;
  4. 監(jiān)聽屬性變化:通過observedAttributes()監(jiān)聽屬性變化讳嘱,當(dāng)屬性改變調(diào)用attributeChangedCallback()方法幔嗦;
  5. 子元素和父元素通信:子元素通過dispatchEvent()向父元素發(fā)送消息,父元素通過監(jiān)聽相應(yīng)事件接收消息沥潭。

有樣式的 Radio

class DCStyledRadio extends HTMLElement {
    constructor() {
        super();
        this.attachShadow({ mode: 'open' });
        this.shadowRoot.innerHTML = `
            <style>
                :host {
                    display: inline-flex;
                    align-items: center;
                    gap: 8px;
                }
                
                input {
                    appearance: none;
                    margin: 0;
                    width: 16px;
                    height: 16px;
                    border-radius: 50%;
                    border: 1px solid #d9d9d9;
                    background-color: #fff;
                }
                
                input:checked {
                    border: 5px solid #1677ff;
                }

                input:disabled {
                    opacity: 0.6;
                    cursor: default;
                }

                input:not(:disabled):is(:hover) {
                    border-color: #1677ff;
                    cursor: pointer;
                }

                label {
                    cursor: pointer;
                }

                :disabled+label {
                    cursor: default;
                }
            </style>
            <input type="radio" id="radio"/>
            <label for="radio">
                <slot></slot>
            </label>      
        `
        this.$input = this.shadowRoot.querySelector('input')
    }

    get value() {
        return this.getAttribute('value') || this.textContent
    }

    get name() {
        return this.getAttribute('name')
    }

    set name(value) {
        this.setAttribute('name', value)
    }

    set disabled(value) {
        this.toggleAttribute('disabled', value)
    }

    set checked(value) {
        this.toggleAttribute('checked', value)

    }

    static get observedAttributes() {
        return ['checked', 'disabled']
    }

    connectedCallback() {
        this.$input.addEventListener('change', () => {
            this.checked = true
            this.dispatchEvent(new CustomEvent('change'))
        })
    }


    attributeChangedCallback(name, oldValue, newValue) {
        this.$input[name] = newValue !== null

        if (name === 'checked' && newValue !== null) {
            document.querySelectorAll(`dc-styled-radio[name=${this.name}][checked]`).forEach(radio => {
                if (radio !== this) {
                    radio.checked = false
                }
            })
        }
    }
}

customElements.define('dc-styled-radio', DCStyledRadio);

Radio Group

嵌套在RadioGroup下的Radio是同一組邀泉,value屬性標(biāo)明當(dāng)前選中的Radio。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>DC-RADIO-GROUP</title>
    <script src="./dc-radio.js" defer></script>
    <script src="./dc-radio-group.js" defer></script>
  </head>
  <body>
    <dc-radio-group name="lan" value="vue">
      <dc-radio value="react">react</dc-radio>
      <dc-radio value="vue">vue</dc-radio>
      <dc-radio value="ng">ng</dc-radio>
    </dc-radio-group>
    <button onclick="handleClick()">TEST</button>
    <script>
      function handleClick() {
        const $radioGroup = document.querySelector("dc-radio-group");
        $radioGroup.addEventListener("change", (e) => {
          console.log(1111, e);
        });
        // $radio.checked = true;
        console.log($radioGroup.value);
      }
    </script>
  </body>
</html>

RadioGroup 實(shí)現(xiàn)

/**
 * 1. 根據(jù) name 屬性钝鸽,設(shè)置 dc-radio 的 name 屬性
 * 2. 根據(jù) value 屬性汇恤,設(shè)置 dc-radio 的 checked 屬性
 */
class DCRadioGroup extends HTMLElement {
    constructor() {
        super();
        this.attachShadow({ mode: "open" });
        this.shadowRoot.innerHTML = `
      <style>
       
      </style>
      <slot></slot>
      `
    }

    get name() {
        return this.getAttribute("name");
    }

    get value() {
        return this.getAttribute("value")
    }

    set value(val) {
        this.setAttribute("value", val);
    }
    static get observedAttributes() {
        return ["value"];
    }
    connectedCallback() {
        this.querySelectorAll('dc-radio').forEach(radio => {
            radio.name = this.name
            radio.addEventListener('change', () => {
                this.value = radio.value
                this.dispatchEvent(new CustomEvent('change', {
                    detail: {
                        value: radio.value
                    }
                }))
            })
        })
    }

    attributeChangedCallback(name, oldValue, newValue) {
        this.querySelectorAll('dc-radio').forEach(radio => {
            radio.checked = radio.value === newValue
        })
    }
}

customElements.define("dc-radio-group", DCRadioGroup);

知識(shí)點(diǎn)

  • 自定義屬性:不能像自有屬性(如onclick等)那樣直接通過點(diǎn)語法(如element.data - custom)在大多數(shù)瀏覽器中訪問。只能通過getAttribute方法來獲取其值拔恰。
  • 腳本執(zhí)行順序:加 defer因谎。
  • 偽元素: ::part() 參考 xy-ui。

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末仁连,一起剝皮案震驚了整個(gè)濱河市蓝角,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌饭冬,老刑警劉巖使鹅,帶你破解...
    沈念sama閱讀 211,561評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異昌抠,居然都是意外死亡患朱,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門炊苫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來裁厅,“玉大人,你說我怎么就攤上這事侨艾≈春纾” “怎么了?”我有些...
    開封第一講書人閱讀 157,162評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵唠梨,是天一觀的道長袋励。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么茬故? 我笑而不...
    開封第一講書人閱讀 56,470評(píng)論 1 283
  • 正文 為了忘掉前任盖灸,我火速辦了婚禮,結(jié)果婚禮上磺芭,老公的妹妹穿的比我還像新娘赁炎。我一直安慰自己,他們只是感情好钾腺,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,550評(píng)論 6 385
  • 文/花漫 我一把揭開白布徙垫。 她就那樣靜靜地躺著,像睡著了一般垮庐。 火紅的嫁衣襯著肌膚如雪松邪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,806評(píng)論 1 290
  • 那天哨查,我揣著相機(jī)與錄音逗抑,去河邊找鬼。 笑死寒亥,一個(gè)胖子當(dāng)著我的面吹牛邮府,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播溉奕,決...
    沈念sama閱讀 38,951評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼褂傀,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了加勤?” 一聲冷哼從身側(cè)響起仙辟,我...
    開封第一講書人閱讀 37,712評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鳄梅,沒想到半個(gè)月后叠国,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,166評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡戴尸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,510評(píng)論 2 327
  • 正文 我和宋清朗相戀三年粟焊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片孙蒙。...
    茶點(diǎn)故事閱讀 38,643評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡项棠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出挎峦,到底是詐尸還是另有隱情香追,我是刑警寧澤,帶...
    沈念sama閱讀 34,306評(píng)論 4 330
  • 正文 年R本政府宣布坦胶,位于F島的核電站翅阵,受9級(jí)特大地震影響歪玲,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜掷匠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,930評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望岖圈。 院中可真熱鬧讹语,春花似錦、人聲如沸蜂科。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽导匣。三九已至才菠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評(píng)論 1 266
  • 我被黑心中介騙來泰國打工战授, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留胞皱,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,351評(píng)論 2 360
  • 正文 我出身青樓舔稀,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子步悠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,509評(píng)論 2 348

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

  • 前言:這周完成了兩場(chǎng)技術(shù)分享會(huì),下周還有一場(chǎng)瘫镇,就完成了這階段的一個(gè)重大任務(wù)鼎兽。分享會(huì)是關(guān)于 TS 的,我這兩場(chǎng)分享會(huì)...
    CondorHero閱讀 978評(píng)論 0 2
  • 現(xiàn)在的前端開發(fā)基本離不開 React铣除、Vue 這兩個(gè)框架的支撐谚咬,而這兩個(gè)框架下面又衍生出了許多定義組件庫: 這些組...
    wan_c1121閱讀 1,080評(píng)論 1 2
  • 組件化是前端工程化重要的一環(huán),UI 和 交互(或邏輯)的復(fù)用極大的提高開發(fā)效率以及減少代碼冗余通孽。 目前開源的組件庫...
    一蓑煙雨任平生_cui閱讀 4,501評(píng)論 0 4
  • 簡單例子 定義 Web Components 是一套不同的技術(shù)序宦,允許您創(chuàng)建可重用的定制元素(它們的功能封裝在您的代...
    copyLeft閱讀 277評(píng)論 0 1
  • Web Components 首先來了解下 Web Components 的基本概念, Web Component...
    涅槃快樂是金閱讀 729評(píng)論 0 3