征服number型別的input框

周驊發表於2017-11-01

寫於2017年11月1日,如有錯漏,歡迎斧正。

原文

在移動端H5頁面開發中,有時候會有一些數值輸入方面的需求。如果需要讓使用者比較方便地輸入小數、負數,最簡單的方式是使用number型別的input框,輸入時軟鍵盤會切換為數字鍵盤方便輸入(ios是帶數字的全鍵盤,並允許1e5這類的表示方法)。但是在實際使用的時候會面臨一個問題,當輸入的內容不合法時,比如輸入了1...2,此時input會設定自己的狀態為invalid,並把value清空。我們可以在css中通過input:invalid來為此input設定輸入內容錯誤的樣式,而在js一端,我們卻無法知道使用者輸入的錯誤內容到底是什麼。這個問題是dom本身的特性所置,與我們採用的開發框架無關,不管我們是使用angular、vue還是react,當number型別的input中輸入不合法內容時,js就是沒有辦法知道錯誤內容是什麼,也就沒有辦法幫助使用者修正輸入錯誤。

然而實際需求可不管這個,限制使用者輸入的數值範圍、小數點後精度、是否允許負數等等都是很常見的需求。那怎麼辦呢?最近看到一篇文章做了一個很無可奈何,但確實很華麗的嘗試:一個數字鍵盤引發的血案——移動端H5輸入框、游標、數字鍵盤全假套件實現。有興趣可以看一下,這篇文章實現了一個假的鍵盤來處理這些邏輯。在很複雜的互動和視覺需求下,做一個假鍵盤也不失為一個終極的解決方案。不過在我看來,處理數字格式、糾錯等問題倒可以不用這麼麻煩,下面我就分享一下我的思路。

設定一個目標吧:

  1. 允許輸入負數
  2. 最多輸入5位數
  3. 最多允許兩位小數
  4. 自動清除多餘的前置0
  5. 自動補充純小數的整數部分0

雖然定了這麼多目標,歸根到底只要能得到輸入框的輸入內容,剩下的就是簡單的字串處理了。那麼如何能獲取輸入內容呢?我的方法是讓瀏覽器選中輸入框中所有的內容,然後獲取瀏覽器中選中的字串,最後清除選中狀態。選中輸入框中的內容有兩種方法:

  1. 最簡單的是直接呼叫input.select()HTMLInputElement.select());
  2. 當輸入框已經獲得焦點的時候(當然),可以使用document.execCommand('SelectAll')來全選輸入框中的所有內容(The selectAll command)。

然而實際上型別的為number的輸入框是不允許操作selectRange的,雖然能選中內容,獲取輸入框中資訊,但我在清除選中狀態時遇到了問題(也許我程式碼寫得不對),那麼採用第二種方法。獲取瀏覽器選中的字串可以用Window.getSelection().toString()Window.getSelection())。得到實際輸入的內容後,我們可以進行一系列的邏輯判斷、數值更正。那麼最後怎麼再把正確的內容寫回input框中,同時清除選中狀態呢?方法也很多:

  1. 把正確的內容寫進clipboard,再貼上到選中區域,主要使用document.execCommand('copy')document.execCommand('paste')The copy commandThe paste command);
  2. 直接執行一次Undo操作,把輸入框內容回退到上一次的正確結果(The undo command);
  3. 執行insertText命令,在選中區域處插入新文字(The insertText command)。

到這裡基本上所需要的準備工作都已經完成,試著實踐一下:

<input type="number" id="input"/>
複製程式碼
const input = document.querySelector('#input');

const format = /-?\d{0,5}(\.\d{0,2})?/;
let lastValue;

input.addEventListener('input', event => {
    document.execCommand('SelectAll');
    let text = window.getSelection().toString();
    if (lastValue !== text) {
        const match = text.match(format);
        if (!match) {
            text = '';
        }
        else {
            text = match[0]
                .replace(/^(-?)0+(\d)/, '$1$2') // 刪除多餘的前置0
                .replace(/^(-?)\./, '$10.'); // 插入純小數的整數0
        }
        lastValue = text;
        document.execCommand('insertText', false, text);
    }
});
複製程式碼

Demo

相關文章