文章分享了,如何一步步优化输入框的监听事件,以达到兼容、高效和组合输入友好等目标。
完善的输入框监听方案
keyup
监听输入框的输入,最原始的方法是使用keyup
事件。
不使用change
事件,它只会在输入框失去焦点后被触发。
此方式兼容性广,但效率较低,毕竟任意的按键都会触发该事件。
<input id="input" type="text" />
<script>
document.querySelector('#input')
.addEventListener('keyup', function() {
console.log('value:', this.value);
});
</script>
input
我们只希望当值发生变化后再触发监听,这样,input
事件出现了。
它只会在输入框的值发生变化后被触发,不过IE8及以下不支持该事件。
网上很多人使用IE独有的propertychange
事件,作为替代input
的方案,这里不推荐。
一方面它会在任意属性值变化后被触发,没有专一性,不够语义,比较浪费。
二方面网上都是用jQuery
等工具库操作,比较简单,而我们的目的是用原生代码实现。
三方面是不支持input
事件的浏览器已经很少了,硬碰上了就用keyup
对付。
<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>
composition
中文、日文等需要借助输入法组合输入,即便是英文,现在也可借助组合输入进行选词等。
实际中,我们希望将用户组合输入完的一段文字,而不是每输入一个字母,算做一次输入的完成。
组合输入事件应运而生,常用的是compositionstart
(组输开始)和compositionend
(组输结束)事件。
结合组合事件不监听普通的输入,以及compositionstart
发生在input
事件之前,可以如此优化中文输入。
<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>
结合
最后是结合以上几步生成一个融合方法,代码加示例:地址。
里面还做了些增强:
比如监听函数返回的是一个,移除这一步所加的所有事件的方法。
比如配置是否监听组合输入事件,因为好的搜索框会直接根据拼音开始搜索,无需等到汉字的形成。
代码使用ES6语法,需使用支持ES6的浏览器(Chrome最新版)或转码后才能使用,谅解。
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);
}
}