使用 tabindex 配合 focus-within 巧妙實現父選擇器

ChokCoco發表於2020-10-30

本文將介紹一個不太實用的小技巧,使用 tabindex 配合 :focus-within 巧妙實現父選擇器。

 

CSS 中是否存在父選擇器?

這是一個非常經典的問題,到目前為止,CSS 沒有真正意義上被廣泛實現的父選擇器,這和瀏覽器的渲染機制有關。

如果你對 CSS 中是否存在父選擇器有疑惑,可以去看看 知乎 -- CSS 中能否選取父元素?

當然,這不代表 CSS 完全無法通過子元素去控制父元素,通過 :focus-within 偽類可以近似的達到類似的目的。

 

:focus-within 偽類

首先需要複習一下 :focus-within,它是一個偽類。

它表示一個元素獲得焦點,或,該元素的後代元素獲得焦點。劃重點,它或它的後代獲得焦點。

關於 :focus-within,不算太瞭解的可以先看看這篇文章:《神奇的選擇器 :focus-within》

利用它,我們可以實現類似這樣的功能,通過元素的子元素的獲焦(focus事件),觸發該偽元素,從而實現一個狹義的父選擇器,類似這樣:

CodePen -- CSS focus-within INPUT

 

:focus-within 偽類實現父選擇的缺陷

藉助 :focus-within 實現父類選擇器最大的問題是,元素必須要有 focus 事件,才能觸發它或者它的父元素的 :focus-within

所以,這就導致了在之前我認為 :focus-within 只能配合 <button><input > 元素一起使用。

諸如 <button><input><select><a> 這類可互動元素,預設是存在 focus 事件的,而類似 <div><span> 和 <table> 這類非互動元素,預設是不能被聚焦的。

也是因為這個原因,大大限制了它的使用場景。基於此,我們引入本文的另外一個主角 -- tabindex

 

使用 tabindex 使元素獲得 focus 事件

tabindex: HTML 標籤的屬性,指示其元素是否可以聚焦,以及它是否/在何處參與順序鍵盤導航(通常使用Tab鍵,因此得名)。

也就是說,一個單純的 div 標籤,他是沒有 focus 事件的,然而,我們給它加上一個 tabindex 屬性,這個時候他就會獲得類似 input 框一樣的表現,擁有了 focus 事件,再配合 :focus-within,能夠使用的場景就大大提升了。

看看虛擬碼:

<div class="g-father">
    <!-- 沒有 focus 事件的 .g-children 元素 -->
    <div class="g-children">Click</div>
</div>
<div class="g-father">
    <!-- 擁有 focus 事件的 .g-children 元素 -->
    <div class="g-children" tabindex="-1">Click</div>
</div>

這裡為什麼是 tabindex="-1" 呢,tabindex 負值表示元素是可聚焦的,但是不能通過鍵盤導航來訪問到該元素。因為我們只需要讓元素能夠獲得 focus 事件,而不需要他真的能夠被鍵盤導航來訪問。

這樣,配合 :focus-within,就能做到當點選子元素的時候,去改變父元素的樣式了。

並且,我們可以在任意元素上搭配 tabindex,脫離了 <input>, <a>, <button> 等元素才有 focus 事件的束縛。

.g-father:focus-within {
    background: #fc0;
}

CodePen -- tabindex 配合 focus-within 實現div的父選擇器

 

一個小細節,button 的 focus 事件在 Safari 和 firefox 的上冒泡問題

由於 input 元素(或者任意元素 +tabindex) 配合 :focus-within 的方案依賴 focus 事件的冒泡。

而對於 <button> 元素,稍微有點特殊,存在這樣兩個問題,即:

  1. 在 MacOS 的 Safari 和 Firefox 中, **點選 <button> 元素,不會觸發 <button> 的 focus 事件,也沒有 focus 事件冒泡。
  2. 在 Windows 的 Safari 和 Firefox 中, 點選 <button> 元素,會觸發 <button> 的 focus 事件,但在被目標元素捕捉到之後,不會繼續向上冒泡。

什麼意思呢?我們來驗證一下,使用類似這樣的結構:

<div class="g-father">
    <input type="button" value="Button">
</div>
input:focus {
    background: #00bcd4;
}

body:focus-within {
    background: blue;
}

.g-father:focus-within {
    background: red;
}

看看,在 Chrome 下的表現:

在 Windows 的 Safari,Firefox 下的表現:

在 MacOS 的 Safari,Firefox 下的表現:

在 Chrome 上的表現是正常,而在 Windows 的 Safari、Firefox 上,會觸發 button 的 focus 事件,但不會觸發父元素的 :focus-within 事件,也就是上面說的,focus 事件,在被目標元素捕捉到之後,不會繼續向上冒泡。而在 Mac 上,則連 focus 都不會觸發。

這一點,在使用的時候務必需要留意。

CodePen -- button 的 focus 事件冒泡性驗證(Chorme / Safari / Firefox)

 

最後

當然,本文介紹的小技巧,只能算是一個非常簡陋,特定條件(點選目標元素改變父元素樣式)下的父選擇器,真正意義上的父選擇器仍需等待未來規範的實現。

好了,本文到此結束,希望對你有幫助 :)

更多精彩 CSS 技術文章彙總在我的 Github -- iCSS ,持續更新,歡迎點個 star 訂閱收藏。

如果還有什麼疑問或者建議,可以多多交流,原創文章,文筆有限,才疏學淺,文中若有不正之處,萬望告知。

相關文章