radio
-
放在最外層的作用是label
,無論是點選在文字還是input上都能夠觸發響擴大滑鼠點選範圍
<label class="el-radio"> <!-- 單選框 --> <span class="el-radio__input"></span> <!-- 文字部分 --> <span class="el-radio__label"></span> </label>
<template> <label class="el-radio" :class="{ [`el-radio--${radioSize || ''}`]: border && radioSize, 'is-disabled': isDisabled, 'is-focus': focus, 'is-bordered': border, 'is-checked': model === label }" role="radio" :aria-checked="model === label" :aria-disabled="isDisabled" :tabindex="tabIndex" @keydown.space.stop.prevent="model = isDisabled ? model : label" > <!-- :tabindex="tabIndex" @keydown.space.stop.prevent="model = isDisabled ? model : label" 這個功能是為了用tab切換不同選項時,按空格可以快速選擇目标項 --> <!-- 模拟圓形按鈕 --> <span class="el-radio__input" :class="{ 'is-disabled': isDisabled, 'is-checked': model === label }" > <!-- 圓形樣式 --> <span class="el-radio__inner"></span> <!-- 真正的按鈕 --> <!-- el-radio__original ⚠️注意這個樣式 --> <input ref="radioRef" v-model="model" class="el-radio__original" :value="label" type="radio" aria-hidden="true" :name="name" :disabled="isDisabled" tabindex="-1" @focus="focus = true" @blur="focus = false" @change="handleChange" > </span> <!-- 文字部分 --> <!-- keydown.stop 阻止事件繼續冒泡 --> <span class="el-radio__label" @keydown.stop> <slot> {{ label }} </slot> </span> </label> </template>
- el-radio__original
真正的input透明度為0,且是絕對定位脫離文檔流,是以我們看不到,但是是有大小的,注意不是.el-radio__original { opacity: 0; outline: none; position: absolute; z-index: -1; top: 0; left: 0; right: 0; bottom: 0; margin: 0; }
或者display:none
,如果是none或者hidden的話則無法觸發滑鼠點選了,隻有visibility:hidden
才能達到目的,這是個需要注意的地方opacity:0
- disabled
樣式上的
綁定在disabled
上,功能上的label
綁定在disabled
上input
const isDisabled = computed(() => { return isGroup.value ? radioGroup.disabled || props.disabled || elForm.disabled : props.disabled || elForm.disabled })
、isGroup
的取值都是通過elForm
注入inject
const radioGroup = inject(radioGroupKey, {} as RadioGroupContext) const isGroup = computed(() => radioGroup?.name === 'ElRadioGroup')
-
role="radio" :aria-checked="model === label" :aria-disabled="isDisabled"
role的作用是描述一個非标準的tag的實際作用。比如用div做button,那麼設定div 的 role=“button”,輔助工具就可以認出這實際上是個button。role
的作用就是描述這個tag在可視化的情境中的具體資訊aria-*
此tag 是一個
,radio
和checked
的屬性分别是什麼disabled
-
tabindex規定了按下tab鍵該元素擷取焦點的順序,同樣是個計算屬性:tabindex="tabIndex" @keydown.space.stop.prevent="model = isDisabled ? model : label"
const tabIndex = computed(() => { return (isDisabled.value || (isGroup.value && model.value !== props.label)) ? -1 : 0 })
- tabindex=負值 (通常是tabindex=“-1”),表示元素是可聚焦的,但是不能通過鍵盤導航來通路到該元素,用JS做頁面小元件内部鍵盤導航的時候非常有用。
-
,表示元素是可聚焦的,并且可以通過鍵盤導航來聚焦到該元素,它的相對順序是目前處于的DOM結構來決定的。tabindex="0"
- tabindex=正值,表示元素是可聚焦的,并且可以通過鍵盤導航來通路到該元素;它的相對順序按照tabindex 的數值
。如果多個元素擁有相同的 tabindex,它們的相對順序按照他們在目前DOM中的先後順序決定。遞增而滞後獲焦
這個功能是為了用tab切換不同選項時,按空格可以快速選擇目标項 (先通過tab 切換聚焦,在通過space選擇目前聚焦的項目)@keydown.space.stop.prevent="model = isDisabled ? model : label"
- focus
//radio.vue const focus = ref(false)//控制focus樣式 click時,不顯示focus樣式 <label :class="{ 'is-focus': focus, ... }"> <input @focus="focus = true" @blur="focus = false">
.el-radio:focus:not(.is-focus):not(:active):not(.is-disabled) .el-radio__inner { box-shadow: 0 0 2px 2px #409eff; }
按
,會出現tab切換
樣式,但不會觸發foucs
因為label上添加的@focus事件
,而input的:tabindex="tabIndex"
,是以雖然有樣式,但不會觸發input的:tabindex="-1"
當@focus事件
時,同時也會出現click
樣式,觸發focus
,此時不想有@focus事件
的樣式,是以添加一個focus
is-focus
- model
const model = computed<string | number | boolean>({ get() { return isGroup.value ? radioGroup.modelValue : props.modelValue }, set(val) { if (isGroup.value) { radioGroup.changeEvent(val)//injdect radioGroup 修改radioGroup的v-model值 } else { ctx.emit(UPDATE_MODEL_EVENT, val)//update:modelValue 修改radio的v-model值 } radioRef.value.checked = props.modelValue === props.label//dom 對象上的 checked 屬性 }, })
通過Model
computed
計算而來,
get : 擷取值
Set: 修改值
- nextTick
此處為什麼使用nextTick?感覺不使用也沒問題❓function handleChange() { nextTick(() => { ctx.emit('change', model.value) }) }
我認為 radio-group 中已確定了執行循序,是以radio的change 無論是單獨使用還是在group内總是最後執行的,是以我認為使不使用
都可以。希望有人幫忙解惑,謝謝nextTick
radio-group
- nextTick
const changeEvent = value => { ctx.emit(UPDATE_MODEL_EVENT, value)//update:modelValue nextTick(() => { ctx.emit('change', value) }) }
此處nextTick作用,保證執行順序。 先 ctx.emit(UPDATE_MODEL_EVENT, value),後 ctx.emit(‘change’, value),保證
變化會優先于v-model
change
執行。
如果沒有 nextTick,執行順序相反,
因為ctx.emit(UPDATE_MODEL_EVENT, value),更新值的變化,會導緻頁面更新,是以會在
。而chang事件會在下次頁面更新時執行
。目前執行
- 選中切換
// '@element-plus/utils/aria' export const EVENT_CODE = { tab: 'Tab', enter: 'Enter', space: 'Space', left: 'ArrowLeft', // 37 up: 'ArrowUp', // 38 right: 'ArrowRight', // 39 down: 'ArrowDown', // 40 esc: 'Escape', delete: 'Delete', backspace: 'Backspace', } //radio-group.vue const handleKeydown = e => { // 左右上下按鍵 可以在radio組内切換不同選項 const target = e.target const className = target.nodeName === 'INPUT' ? '[type=radio]' : '[role=radio]'//radio||label const radios = radioGroup.value.querySelectorAll(className) const length = radios.length const index = Array.from(radios).indexOf(target) const roleRadios = radioGroup.value.querySelectorAll('[role=radio]') let nextIndex = null switch (e.code) { case EVENT_CODE.left: case EVENT_CODE.up: //⬅️⬆️ 選中前一個 e.stopPropagation()//阻止冒泡 e.preventDefault()//阻止預設行為 //如果目前選中的是第一個,則下一個選中的是最後一個,否則是前一個 nextIndex = index === 0 ? length - 1 : index - 1 break case EVENT_CODE.right: case EVENT_CODE.down: //➡️⬇️ 選中後一個 e.stopPropagation() e.preventDefault() //如果目前選中的是最後一個,則下一個選中的是第一個,否則是後一個 nextIndex = (index === (length - 1)) ? 0 : index + 1 break default: break } if (nextIndex === null) return roleRadios[nextIndex].click() roleRadios[nextIndex].focus() }
radio-button
-
放在最外層的作用是label
,無論是點選在文字還是input上都能夠觸發響擴大滑鼠點選範圍
- 同上
.el-radio-button__orig-radio { opacity: 0; outline: none; position: absolute; z-index: -1; }