完善的輸入框監聽方案:兼容、高效和組合輸入友好

完善的輸入框監聽方案

keyup

監聽輸入框的輸入,最原始的方法是使用keyup事件。
不使用change事件,它只會在輸入框失去焦點後被觸發。
此方式兼容性廣,但效率較低,畢竟任意的按鍵都會觸發該事件。瀏覽器

<input id="input" type="text" />

<script>
  document.querySelector('#input')
  .addEventListener('keyup', function() {
    console.log('value:', this.value);
  });
</script>

input

咱們只但願當值發生變化後再觸發監聽,這樣,input事件出現了。
它只會在輸入框的值發生變化後被觸發,不過IE8及如下不支持該事件。app

網上不少人使用IE獨有的propertychange事件,做爲替代input的方案,這裏不推薦。
一方面它會在任意屬性值變化後被觸發,沒有專注性,不夠語義,比較浪費。
二方面網上都是用jQuery等工具庫操做,比較簡單,而咱們的目的是用原生代碼實現。
三方面是不支持input事件的瀏覽器已經不多了,硬碰上了就用keyup對付。dom

<input id="input" type="text" />

<script>
  document.querySelector('#input')
  .addEventListener('input', function() {
    console.log('value:', this.value);
  });
</script>

接着上步,如何在不支持input事件時使用keyup事件呢?
直接檢測事件不太靠譜,能夠利用input在keyup以前發生的性質,巧妙的實現此功能。函數

<input id="input" type="text" />

<script>
  let inInputEvent = false;
  let input = document.querySelector('#input');
  
  input.addEventListener('keyup', function() {
    if (inInputEvent) {
      // You can remove keyup listener.
    } else {
      console.log('keyup:', this.value);
    }
  });
  input.addEventListener('input', function() {
    if (!inInputEvent) inInputEvent = true;
    console.log('input:', this.value);
  });
</script>

延遲函數

在搜索功能中,理想化的情景是當用戶所有輸入後,再當即執行搜索。
那麼問題來了,如何在不須要用戶點擊搜索按鈕的狀況下,得知其過程的完成呢?沒有辦法。
雖然沒有辦法,但有優化的方式:假定用戶每一個單詞的輸入間隔,以此時間延遲執行搜索功能。工具

英文通常爲 300ms ,中文可設置成 500ms 。

<input id="input" type="text" />

<script>
  let input = document.querySelector('#input');
  let trigger = createDelayFunction(console.log);
  
  input.addEventListener('input', function() {
    trigger(this.value);
  });

  function createDelayFunction(fn, timeout = 300) {
    let timeoutId = -1;
    return (...args) => {
      clearTimeout(timeoutId);
      timeoutId = setTimeout(() => {
        fn.apply(null, args);
      }, timeout);
    }
  }
</script>

組合

中文、日文等須要藉助輸入法組合輸入,即使是英文,如今也可藉助組合輸入進行選詞等。
實際中,咱們但願將用戶組合輸入完的一段文字,而不是每輸入一個字母,算作一次輸入的完成。優化

組合輸入事件應運而生,經常使用的是compositionstart(組輸開始)和compositionend(組輸結束)事件。
結合組合事件不監聽普通的輸入,以及compositionstart發生在input事件以前,能夠如此優化中文輸入this

<input id="input" type="text" />

<script>
  let inCompositionEvent = false;
  let input = document.querySelector('#input');
  
  input.addEventListener('input', function() {
    !inCompositionEvent && console.log('input', this.value);
  });
  input.addEventListener('compositionstart', function() {
    inCompositionEvent = true;
  });
  input.addEventListener('compositionend', function() {
    inCompositionEvent = false;
    console.log('composition', this.value);
  });
</script>

結合

最後是結合以上幾步生成一個融合方法,代碼加示例:code

裏面還作了些加強:
好比監聽函數返回的是一個,移除這一步所加的全部事件的方法。
好比配置是否監聽組合輸入事件,由於好的搜索框會直接根據拼音開始搜索,無需等到漢字的造成。事件

代碼使用ES6語法,需使用支持ES6的瀏覽器(Chrome最新版)或轉碼後才能使用,諒解ip

function listenInput(dom, callback, {
  timeout = 300,
  useCompositionEvent = true
} = {}) {
  let value = '';
  let inInputEvent = false;
  let inCompositionEvent = false;
  let trigger = createDelayFunction(valueChanged, timeout);

  // Return a function that can remove listeners added here.
  return enabledEvent(dom);

  function valueChanged(val) {
    if (val === value) {
      return ;
    } else {
      value = val;
    }

    callback(value, { dom: dom });
  }

  function enabledEvent(dom) {
    dom.addEventListener('keyup', keyup);
    dom.addEventListener('input', input);
    useCompositionEvent && dom.addEventListener('compositionstart', compositionstart);
    useCompositionEvent && dom.addEventListener('compositionend', compositionend);

    return function() {
      dom.removeEventListener('keyup', keyup);
      dom.removeEventListener('input', input);
      useCompositionEvent && dom.removeEventListener('compositionstart', compositionstart);
      useCompositionEvent && dom.removeEventListener('compositionend', compositionend);
    };

    function keyup() {
      if (inInputEvent) {
        dom.removeEventListener('keyup', keyup);
      } else {
        trigger(this.value);
      }
    }
    
    function input() {
      if (!inInputEvent) inInputEvent = true;
      if (!inCompositionEvent) trigger(this.value);
    }
    
    function compositionstart() {
      inCompositionEvent = true;
    }
    
    function compositionend() {
      inCompositionEvent = false;
      trigger(this.value);
    }
  }
}

function createDelayFunction(fn, timeout = 300) {
  let timeoutId = -1;
  return (...args) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => {
      fn.apply(null, args);
    }, timeout);
  }
}
相關文章
相關標籤/搜索