最近遇到的前端小需求,分類項目清單允許使用者勾選整個類別,也可以展開類別勾選個別項目,操作起來類似以下這個樣子:

這種互動操作不難實作,弄個 { Catg: 'FrontEnd', Name: 'Vue.js', Checked: false } 物件模型,拿出 Vue.js 簡單搭一下,插電、開機、輕鬆秒殺,用起來也沒什麼問題。

但有點美中不足 - 以上面的示範為例,從類別清單看不出來 FrontEnd 及 Web 類別下有項目被勾選。

於是我幫自己訂了個題目,希望做到類別下有項目被勾選時,類別 Checkbox 要呈現部分選取狀態(灰色底、淺色勾勾之類的效果)以示區別。

研究了一下,決定用 CSS ::before 虛擬元素實現,當類別下有項目被選取時,用 Vue v-bind:class 加上 partial 樣式,並設定以下樣式:

input.partial {
    position: relative;
}
input.partial::before {
    content: '\2714';
    font-size: 0.8em;
    position: absolute;
    top: -2px;
    left: 2px;
    opacity: 0.8;
}
input.partial:checked::before {
    display: none;
}

input.partial 設定 position: relative 允許 before 虛擬元素能用 position: absolute 彈性調整顯示位置;虛擬元素用 content 放上「✔」(U+2714) 勾勾符號,用 top/left 調座標以剛好嵌入方格正中央,最後加上 opacity 做成半透明。當 Checkbox 被勾選時 ✔ 要隱藏,用 input.partialchecked:before 選擇器配上 display: none 便大功告成。

試了 Chrome/Edge/Safari 都 OK,要開心收工前試了一下 Firefox,可惡,這招在 Firefox 上不管用(早知裝沒事就算了)。

爬文得知在 Firefox ::before/::after 只對容器元素有效,無法用在 Checkbox 上,得另外想法子。

我的第一個嘗試是在 input 外包一層 span,改用 span:has(input.partial)::before 插入虛擬元素,很不幸,Firefox 不支援 :has()

:has() 不能用,span 放後面再用 input.partial + span::before (Adjacent Sibling Combinator (+)) 總行了吧?

<input type="checkbox" class="partial" /><span></span>
<style>
input.partial + span {
    position: relative;
    margin: 0;
}
input.partial + span::before {
    content: '\2714';
    position: absolute;
    font-size: 0.8em;
    top: -2px;
    left: -14px;
    opacity: 0.4;
}
input.partial:checked + span::before {
    display: none;
} 
</style>

顯示是成功了,但 span::before 虛擬元件會蓋在 Checkbox 上方使它沒法被勾選啊啊啊啊~~

幸好,前陣我學會一個新武器 pointer-events,能讓元素對滑鼠操作隱形,在 input.partial + span::before 加上 pointer-events: none,終於,成功跨越 Chrome/Edge/Safari/Firefox。(下圖為 Firefox 操作展示)

線上展示

其實我的應用只需要支援 Chrome/Edge 就可以了,不過跨瀏覽器總是能逼你學會更多,啊,福氣啦!

【2023-08-04 更新】

貼完文章,感謝讀者 Chester Fung 分享更完美的原生 API 解法 - Checkbox 有所謂的 Indeterminate State / 狀態未定,Checkbox 有個 indeterminate 屬性,設為 true 時勾選方格會呈現第三種狀態(在 Edge 是方格中有一條橫線);indeterminate 是元素 Property 不是 Attribute,Vue.js 要設定其值需使用 propName.prop 修飾詞或用 .propName 縮寫。所以其實可以完全不用 CSS,加上 .indeterminate="ElementSelected(c.Name)" 就搞定了:

<input type="checkbox" v-model="c.Checked" .indeterminate="ElementSelected(c.Name)" />

如果覺得呈現樣式不好看,可透過 CSS input:indeterminate 選擇器自訂。但有個小缺點是設定 indeterminate=true 後,使用者勾選或取消勾選該屬性會被清掉,需自己掛載事件或透過變數連動把它設回去。

線上展示

Example of using CSS and Vue.js to imeplement a checkbox which can dispaly a partially selected. checkbox state.


Comments

Be the first to post a comment

Post a comment