兼顧pc和移動端的textarea字數監控的實現方法

1、需求闡述和經常使用的解決方案

製做一個文本框限制最大字數,實時監聽當前已經輸入的字數,並顯示出來。期初我實現這個功能的方法很簡單:給textarea控件添加onkeyup事件方法,在方法中將textarea值的長度打印出來,並給textarea添加一個maxlength屬性設置長度限制便可。代碼以下:javascript

<div class="form-group needCount">        
    <textarea id="txt0" maxlength="10"></textarea>
    <p><span id="txtNum0">0</span>/10</p>
</div>
var txt0 = document.getElementById("txt0");
var txtNum0 = document.getElementById("txtNum0");
txt0.addEventListener("keyup",function(){
    txtNum0.textContent = txt0.value.length;
});

2、存在的問題

這樣貌似很簡單就實現了,在英文輸入法下一切還都ok,但當咱們用輸入法輸入中文時,問題很快就來了,好比咱們要輸入「文章」一詞就要輸入「wenzhang」瀏覽器會監聽到8詞keyup事件。在一些瀏覽器(如safari)中,若是這個過程超過maxlength甚至會阻止你繼續輸入。所以單純的監聽keyup事件顯示是不夠的。html


每次按下鍵盤都會觸發
每次按下鍵盤就會觸發監聽事件java

3、問題的解決

通過查閱前輩們的解決方案,發現了兩個以前沒有據說過的屬性「compositionstart」和「compositionend」。
MDN上的解釋:compositionstart 事件觸發於一段文字的輸入以前(相似於 keydown 事件,可是該事件僅在若干可見字符的輸入以前,而這些可見字符的輸入可能須要一連串的鍵盤操做、語音識別或者點擊輸入法的備選詞)。
compositionend就是對應的就是一段文字輸入的事件。ios

有了這兩個事件,咱們就能夠作一個「開關」,一旦檢測到開始使用輸入法輸入一段文字了,就把這個「開關打開」,檢測到一段文字輸入完畢了,就關閉這個「開關」。接下來咱們在以前的keyup方法中添加一個斷定條件,若是開關關閉,正常打印出textaarea值的長度;若是開關打開,中止打印。而輸入一段文字時,監聽輸入完成的事件「compositionend」,將textarea的值的長度打印出來。這樣不管是否開啓了輸入法都能正確的打印出控件值的長度了。數組

代碼以下:(其中變量chnIpt就是表明是否開啓了輸入法進行輸入的關鍵變量「開關」)瀏覽器

var txt0 = document.getElementById("txt0");
    var txtNum0 = document.getElementById("txtNum0");
    var chnIpt0 = false;

    txt0.addEventListener("keyup",function(){
        if(chnIpt0 ==false){
            countTxt();
        }
    });
    txt0.addEventListener("compositionstart",function(){
        chnIpt0 = true;
    })
    txt0.addEventListener("compositionend",function(){
        chnIpt0 = false;
        countTxt();
    })
    function countTxt(){
        if(chnIpt0 == false){
            txtNum0.textContent = txt0.value.length;
        }
    }

如此實現的效果就是英文輸入法下沒放開鍵盤就會進行一次字數統計,輸入法輸入中文時,輸入結束時纔會統計字數。優化

4、實現複用

固然一個完整的插件必定是能夠複用的。若是頁面裏須要多個文本框都要限制字數如何實現。
咱們須要考慮如下幾個問題:spa

  1. 關鍵元素(文本框txt和用於顯示字數的txtNum)的變量建立和元素獲取如何實現?
  2. 同是監聽「keyup」和「compositionend」如何區分不一樣的textarea

要解決問題1,首先想到建立一個數組,數組中的每個元素經過不一樣的Id獲取一個元素。一個獨立的過程當中咱們須要獲取兩個元素:txt和txtNun,一個關鍵變量chnIpt,所以咱們要建立三個數組。爲了方便理解,假定頁面中有須要三組控件:插件

<div class="form-group needCount">        
        <textarea id="txt0" maxlength="10" onfocus="ff(0)"></textarea>
        <p><span id="txtNum0">0</span>/10</p>
    </div>
    <div class="form-group needCount">        
        <textarea id="txt1" maxlength="10" onfocus="ff(1)"></textarea>
        <p><span id="txtNum1">0</span>/10</p>
    </div>
    <div class="form-group needCount">        
        <textarea id="txt2" maxlength="10" onfocus="ff(2)"></textarea>
        <p><span id="txtNum2">0</span>/10</p>
    </div>

則建立數組的過程:code

var txt0 = document.getElementById("txt0");
    var txt1 = document.getElementById("txt1");
    var txt2 = document.getElementById("txt2");

    var txtNum0 = document.getElementById("txtNum0");
    var txtNum1 = document.getElementById("txtNum1");
    var txtNum2 = document.getElementById("txtNum2");

    var chnIpt0 = false;
    var chnIpt1 = false;
    var chnIpt2 = false;

    var txt=[txt0,txt1,txt2];
    var txtNum=[txtNum0,txtNum1,txtNum2];
    var chnIpt=[chnIpt0,chnIpt1,chnIpt2];

這樣txt就是textarea控件的數組,txtNum就是現實字數的標籤的數組,chnIpt就是判斷「開關」的關鍵變量數組,以待調用。

如今思考第二個問題「同是監聽「keyup」和「compositionend」如何區分不一樣的textarea」。或者說,咱們怎麼判斷當前輸入的textarea是txt元素中的第幾個呢。

這裏就須要表單控件都具備的focus事件進行區別,在focus事件的方法中傳入表明數組索引的參數,從而選擇調用數組中相應那個元素。

代碼以下:(ff(i)即爲focus事件調用的方法參數爲索引值)

function ff(i){
    txt[i].addEventListener("keyup",function(){
        if(chnIpt[i] ==false){
            txtNum[i].textContent = txt[i].value.length;
        }
    });
    txt[i].addEventListener("compositionstart",function(){
        chnIpt[i] = true;
    });
    txt[i].addEventListener("compositionend",function(){
        chnIpt[i] = false;
        txtNum[i].textContent = txt[i].value.length;
    });
}

咱們再來考慮最後一個問題。目前是已知頁面中須要幾組文本框的狀況,咱們能夠手動建立,費時費力代碼也不美觀。

進一步優化一下建立數組的過程:給每個獨立的組件一個class,獲取具備這個class的元素的長度,循環這個class的長度,給數組中添加元素便可。如此處理,引用一段腳本沒必要再作更改,只須要在html中添加相應的組件便可。
JS代碼以下

var txt = [],txtNum = [],chnIpt = [];
var needCount = document.getElementsByClassName("needCount");

for(var i=0;i<needCount.length;i++){
    txt[i] = document.getElementById("txt"+i);
    txtNum[i] = document.getElementById("txtNum"+i);
    chnIpt[i] = false;
}

最終完整的JS代碼:

var txt = [],txtNum = [],chnIpt = [];
var needCount = document.getElementsByClassName("needCount");

for(var i=0;i<needCount.length;i++){
    txt[i] = document.getElementById("txt"+i);
    txtNum[i] = document.getElementById("txtNum"+i);
    chnIpt[i] = false;
}

function ff(i){
    txt[i].addEventListener("keyup",function(){
        if(chnIpt[i] ==false){
            txtNum[i].textContent = txt[i].value.length;
        }
    });
    txt[i].addEventListener("compositionstart",function(){
        chnIpt[i] = true;
    });
    txt[i].addEventListener("compositionend",function(){
        chnIpt[i] = false;
        txtNum[i].textContent = txt[i].value.length;
    });
}

一組字符輸入徹底以前不會監聽自述變化
一組字符輸入徹底以前不會監聽自述變化


demo連接:點我

5、兼容性

如此實現知識該組件我嘗試以來最爲接近預期的實現方法,在pc端主流瀏覽器(ie9以上),安卓、ios的原生鍵盤輸入、「訊飛語音」的語音輸入法效果良好。但在一些特定狀況下仍然會出現問題,譬如:

  1. 沒法監聽ios自帶輸入法的語音識別輸入
  2. 獵豹瀏覽器下使用鼠標點選詞組會結算「keyup」而不是「compositionend」,而且會使maxlength失效。再次監聽到「keyup」恢復正常。

以上兩個問題至今沒有找到解決,以及,我發如今火狐瀏覽器下會執行兩次「compositionend」,但並不影響字數統計。諸如這些疑問還煩請網上的各位高人如遇到相似問題給予在下指正與指點。

最後我想說一句話:「若是事件再也不有瀏覽器之間的差別,世界將變成美好的人間!」

相關文章
相關標籤/搜索