CSS魔法堂:改變單選框顏色就這麼吹毛求疵!

前言

 是否曾經被業務提出"能改改這個單選框的顏色吧!讓它和主題顏色搭配一下吧!",而後苦於原生不支持換顏色,最後被迫本身手擼一個湊合使用。若拋開input[type=radio]從新開發一個,發現要模擬選中、未選中、不可用等狀態很繁瑣,而涉及單選框組就更煩人了,其實咱們能夠經過label::before:checkedtabindex,而後外加少許JavaScript腳本就能很好地模擬出一個樣式更豐富的「原生」單選框。下面咱們一塊兒來嘗試吧!html

單選框瞭解一下

 因爲咱們的目標是改變單選框顏色,其餘外觀特徵和行爲與原來的單選框一致,那麼咱們就必須先了解單選框原來的外觀特徵和行爲主要有哪些。
1.外觀特徵
1.1.常態樣式ios

margin: 3px 3px 0px 5px;
border: none 0;
padding: 0;
box-sizing: border-box;
display: inline-block;
line-height: normal;
position: static;

注意:外觀上咱們必需要保證佈局特性和原生的一致,不然採用自定義單選框替換後很大機會會影響總體的佈局,最後致使被迫調整其餘元素的佈局特性來達到總體的協調,從而擴大了修改範圍。web

1.2.得到焦點的樣式app

outline-offset: 0px;
outline: -webkit-focu-ring-color auto 5px;

注意:這裏的獲取焦點的樣式僅經過鍵盤Tab鍵才生效,若經過鼠標點擊雖然單選框已得到焦點,但上述樣式並不會生效。框架

1.3.設置爲disabled的樣式佈局

color: rgb(84, 84, 84);

2.行爲特徵
 單選框的行爲特徵,明顯就是選中與否,及選中狀態的改變事件,所以咱們必須保持對外提供change事件。
 另外值得注意的是,當經過鍵盤的Tab鍵讓單選框得到焦點後,再按下Space鍵則會選中該單選框。spa

 有了上述的瞭解,咱們能夠開始着手擼代碼了!code

少廢話,擼代碼

上圖中左側就是原生單選框,右側爲咱們自定義的單選框。從上到下依次爲未選中選中得到焦點disabled狀態的樣式。orm

CSS部分htm

label.radio {
  /* 保證佈局特性保持一致 */
  margin: 3px 3px 0px 5px;
  display: inline-block;
  box-sizing: border-box;

  width: 12px;
  height: 12px;
}

.radio__appearance{
  display: block; /* 設置爲block則不受vertical-align影響,從而不會意外影響到.radio的linebox高度 */
  position: relative;
  box-shadow: 0 0 0 1px tomato; /* box-shadow不像border那樣會影響盒子的框高 */
  border-radius: 50%;
  height: 90%;
  width: 90%;
  text-align: center;
}
label.radio [type=radio] + .radio__appearance::before{
  content: "";
  display: block;
  border-radius: 50%;
  width: 85%;
  height: 85%;

  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);

  transition: background .3s;
}
label.radio [type=radio]:checked + .radio__appearance::before{
  background: tomato;
}
label.radio [type=radio][disabled] + .radio__appearance{
  opacity: .5;
}
label.radio:focus{
  outline-offset: 0px;
  outline: #999 auto 5px;
}
/* 經過鼠標單擊得到焦點時,outline效果不生效 */
label.radio.clicked{
  outline: none 0;
}
/* 自定義單選框的行爲主要是基於原生單選框的,所以先將原生單選框隱藏 */
label.radio input {
  display: none;
}

HTML部分

<!-- 未選中狀態 -->
<label class="radio" tabindex="0">
  <input type="radio" name="a">
  <i class="radio__appearance"></i>
</label>

<br>

<!-- 選中狀態 -->
<label class="radio" tabindex="0">
  <input type="radio" name="a" checked>
  <i class="radio__appearance"></i>
</label>

<br>

<!-- disabled狀態 -->
<label class="radio">
  <input type="radio" name="a" disabled>
  <i class="radio__appearance"></i>
</label>

JavaScript部分

var radios = document.querySelectorAll(".radio")
radios.forEach(radio => {
  // 模擬鼠標點擊後:focus樣式無效
  radio.addEventListener("mousedown", e => {
    var tar = e.currentTarget
    tar.classList.add("clicked")
    var fp = setInterval(function(){
      if (document.activeElement != tar){
        tar.classList.remove("clicked")
        clearInterval(fp)
      }
    }, 400)
  })
  // 模擬經過鍵盤得到焦點後,按`Space`鍵執行選中操做
  radio.addEventListener("keydown", e => {
    if (e.keyCode === 32){
      e.target.click()
    }
  })
})

這個實現有3個注意點:

  1. 經過label傳遞鼠標點擊事件到關聯的input[type=radio],所以能夠安心隱藏單選框又能夠利用單選框自身特性。但因爲label控件自身的限制,如默認不是可得到焦點元素,所以沒法傳遞鍵盤按鍵事件到單選框,即便添加tabindex特性也需手寫JS來實現;
  2. 當tabindex大於等於0時表示該元素能夠得到焦點,爲0時表示根據元素所在位置安排得到焦點的順序,而大於0則表示越小越先得到焦點;
  3. 因爲單選框的displayinline-block,所以單選框將影響line box高度。當自定義單選框內元素採用inline-block時,若vertical-align設置稍有不慎就會致使內部元素所在的line box被撐高,從而致使自定義單選框所在的line box高度變大。所以這裏採用將內部元素的display均設置爲block的作法,直接讓vertical-align失效,提升可控性。

經過opacity:0實現(2018/10/5追加)

 上面咱們經過label關聯display:noneinput[type=radio]從而利用input[type=radio]簡化自定義單選框的實現,但依然要手寫JS實現按Space鍵選中的行爲特徵,有沒有另外一種方式能夠更省事呢?咱們只是想讓用戶看不到原生單選框,那麼直接設置爲opacity:0不就能夠了嗎?!
CSS部分

.radio {
  /* 保證佈局特性保持一致 */
  margin: 3px 3px 0px 5px;
  display: inline-block;
  box-sizing: border-box;

  width: 13px;
  height: 13px;
}
/* 自定義單選框的行爲主要是基於原生單選框的,所以先將原生單選框透明,且沾滿整個父元素 */
.radio input {
  opacity: 0;
  position: absolute;
  z-index: 1; /* 必須覆蓋在.radio__appearance上才能響應鼠標事件 */
  width: 100%;
  height: 100%;
}
.radio__container-box{
  position: relative;
  width: 100%;
  height: 100%;
}
.radio__appearance{
  display: block; /* 設置爲block則不受vertical-align影響,從而不會意外影響到.radio的linebox高度 */
  position: relative;
  box-shadow: 0 0 0 1px tomato; /* box-shadow不像border那樣會影響盒子的框高 */
  border-radius: 50%;
  height: 90%;
  width: 90%;
  text-align: center;
}
.radio [type=radio] + .radio__appearance::before{
  content: "";
  display: block;
  border-radius: 50%;
  width: 85%;
  height: 85%;

  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);

  transition: background .3s;
}
.radio [type=radio]:checked + .radio__appearance::before{
  background: tomato;
}
.radio [type=radio][disabled] + .radio__appearance{
  opacity: .5;
}
.radio:focus-within .radio__appearance{
  outline-offset: 0px;
  outline: #999 auto 5px;
}
/* 經過鼠標單擊得到焦點時,outline效果不生效 */
.radio.clicked .radio_appearance{
  outline: none 0;
}

HTML部分

<!-- 未選中狀態 -->
<span class="radio">
  <span class="radio__container-box">
    <input type="radio" name="a">
    <i class="radio__appearance"></i>
  </span>
</span>

<br>

<!-- 選中狀態 -->
<span class="radio">
  <span class="radio__container-box">
    <input type="radio" name="a" checked>
    <i class="radio__appearance"></i>
  </span>
</span>

<br>

<!-- disabled狀態 -->
<span class="radio">
  <span class="radio__container-box">
    <input type="radio" name="a" disabled>
    <i class="radio__appearance"></i>
  </span>
</span>

JavaScript部分

var radios = document.querySelectorAll(".radio")
radios.forEach(radio => {
  // 模擬鼠標點擊後:focus樣式無效
  radio.addEventListener("mousedown", e => {
    var tar = e.currentTarget
    tar.classList.add("clicked")
    var fp = setInterval(function(){
      if (!tar.contains(document.activeElement){
        tar.classList.remove("clicked")
        clearInterval(fp)
      }
    }, 400)
  })
})

總結

 對於複選框咱們能夠稍加修改就能夠了,而後經過VUE、React等框架稍微封裝一下提供更簡約的API,使用起來就更方便了。
 尊重原創,轉載請註明來自:http://www.javashuo.com/article/p-zyycqwav-cq.html ^_^肥仔John

相關文章
相關標籤/搜索