最簡 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);
- 定義自定義元素:通過繼承HTMLElement創(chuàng)建自定義元素,通過customElements.define()定義元素標(biāo)簽隘马;
- 定制元素內(nèi)容:通過shadowDom來承載元素內(nèi)容,包括樣式和結(jié)構(gòu)妻顶;
- 讀取屬性:由于屬性不是原生標(biāo)簽的屬性酸员,所以需要通過 getAttribute() 獲取屬性值;
- 監(jiān)聽屬性變化:通過observedAttributes()監(jiān)聽屬性變化讳嘱,當(dāng)屬性改變調(diào)用attributeChangedCallback()方法幔嗦;
- 子元素和父元素通信:子元素通過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。