使用純 CSS 實現仿 Material Design 的 input 過渡效果

shanyue發表於2019-04-26

以前一段時間,基於對 nextgraphql 的調研,再加上本人的興趣,我做了一個站點,也作為我以後各種技術折騰,實踐以及興趣交匯的試驗田。

最近我需要在我的實驗田使用 jwt 實踐校驗碼的功能。校驗碼,就是指註冊時郵箱或者簡訊的校驗碼。需要校驗碼則需要有登入註冊,而在登入註冊時,為了多寫一些 CSS,我決定實現一個 Material Design 的表單過渡效果。

實現效果見 詩詞絃歌 - 登入

開始之前,你先看看是否認識以下幾個選擇器。如果不,那麼通過本文你可以學習到以下幾個選擇器,以及他們的試用場景

  • :not(:empty)
  • input:not([value=""])
  • input:valid
  • input:not(:placeholder-shown)

本文地址見 shanyue.tech/post/login-…

問題概述

至於 Material Design 是什麼樣的效果,如上所示。實現以上效果,可以簡單把問題歸結為以下兩點的實現

  1. 當 input 中沒有值且沒有獲得焦點時,hint 資訊灰色呈現在 input 框內
  2. 當 input 獲取到焦點或者有值時,hint 資訊以動畫形式位移到 input 上方

直接把 html 和 css 程式碼貼上來,先來完成簡單的功能

label 置於 input 後邊,方便通過 ~+ 選擇器定位。

form
  .input-wrapper
    input[type="text"]
    label
  .input-wrapper
    input[type="password"]
    label
複製程式碼

CSS 程式碼如下,label 初始時通過絕對定位置於 input 的 placeholder 位置,input 獲得焦點時的 label 的 CSS Selector 也很容易通過 input:focus + label 確定

label {
  /* 定位到input框中 */
  position: absolute;
  bottom: 10px;
  left: 0;
  font-size: 1.2em;
  color: #ccc;
  transition: all ease 0.3s;
  pointer-events: none;
}

input:focus + label {
  /* 定位到 input 上方 */
  color: #f60;
  font-size: 0.8em;
  transform: translateY(-150%);
}
複製程式碼

已經在 20% 的時間內完成了 80% 的工作量,還有剩下 20% 的問題總結如下

  1. 因被 label 遮擋,無法在 label 文字區域點選 input
  2. 獲得焦點的 CSS Selector 很簡單,還有一種複雜的情況是 input 非空值時的 CSS Selector

焦點

pointer-events 用來控制滑鼠點選的行為,如果要實現透過 label 點選,可以設定該屬性為 none

label {
  pointer-events: none;
}
複製程式碼

input:not(:empty)

我條件性反射想到用這個來匹配值非空的 input。但我仔細查了下文件,發現它是不適用的。

根據 empty-pesudo Selectors 描述為

The :empty pseudo-class represents an element that has no children at all. In terms of the document tree, only element nodes and content nodes (such as DOM text nodes, CDATA nodes, and entity references) whose data has a non-zero length must be considered as affecting emptiness;

我來大致翻譯一下,如果一個元素, 它的子節點數為 0,那麼它將匹配到 :empty。這和 input 的 value 風馬牛不相及了。

那如何獲取元素 element 子節點的個數呢? 使用 element.childNodes.length

參考 stackoverflow :not(:empty) CSS selector is not working?

input:not([value=""])

那麼再簡單粗暴些,直接匹配屬性 value 是不就可以了。

No. 不可以。input 中的顯示值並不等同與屬性 value。

那這就引出了下一個問題

如何獲取 input 的值

用以下程式碼測試一下

<input type="text" id="input">
複製程式碼
input.value // ''
input.getAttribute('value') // null

input.setAttribute('value', 4)
input.value // '4',value 值同步過來了
input.getAttribute('value') // '4'

input.value = 3
input.value // '3'
input.getAttribute('value') // '4'
複製程式碼

結論:

  1. input 通過 input.value 獲取值,而非 input.getAttribute('value')
  2. 如果 input.value 為空,那麼可以通過 input.setAttribute('value', value) 設定值,並且會同時修改 input.value
  3. 如果手動為 input.value 賦值後,則使用 input.setAttribute('value', value) 無效

那麼匹配值非空的 input,可以更深理解為 匹配input.value為空的input

這說明在純css實現時無法使用 input:not([value=""]) 作為選擇器

使用 js

思路很簡單,同步 input.valueinput.getAttribute('value')

<input onkeyup="this.setAttribute('value', this.value);" />
複製程式碼

使用 React

受控的 input 元件通過手動控制屬性 value,來設定 input 的值。

這時再結合使用 input:not([value=""]) 選擇器可以成功控制 input 的過渡效果

而我專案中也是在使用 React,使用這一選擇器可以很好的滿足我的需求

input:valid

在 html5 中,input 的 type 新增了以下型別

  • email
  • number
  • search
  • url
  • datetime
  • ...

並且新增了檢驗方式,如是否必須,正則等

  • max/min,最大最小值
  • pattern,正則
  • required,是否必填

這裡引入一個新的選擇器 input:required,匹配所有擁有合法值的 input

我們可以對所有 input 新增 required 的標誌,此時 input:valid 的含義即匹配有值時的 input,順利解決問題

但是使用 input:valid 也有一些限制,如以下兩點

  1. 所有的 input 必須是 required,選填的也必須作為 required,失去了語義化,且提交時會有提示無法正常提交
  2. 無法使用 pattern,email 等複雜 input 的校驗

校驗提示觸發時機

formsubmit 事件觸發後,會引發瀏覽器自帶的校驗提示

formsubmit 事件觸發需要滿足以下兩個條件

  1. 包裹在 form 下
  2. 點選form下 input[type="submit"] 或者 button 進行提交

對於 input:valid 的兩點限制,可以通過把 form 改成 div 解決,此時無法有 submit 事件,選填項也可以正確處理。至於複雜表單校驗,則通過js控制

input:not(:placeholder-shown)

見名思意,:placeholder-shown 此選擇器匹配是否有 placeholder,既然有 placeholder,那麼 input 就沒有 value

它有一個必要條件,placeholder 屬性不能為空,如果實在不必要可以設定為空字串

<input placeholder=" " />
複製程式碼

參考


關注公眾號山月行,在這裡記錄了我的技術成長,歡迎交流

歡迎關注公眾號山月行,記錄我的技術成長,歡迎交流

相關文章