一、 什么是合成事件
DOM3 Event 新增了合成事件(CompositionEvent ), 用于处理通常使用 IME 输入时的复杂输入序列。
二、合成事件常见事件
- compositionstart:文本合成系统如 IME(即输入法编辑器)开始新的输入合成时会触发 compositionstart 事件。
- compositionupdate :事件触发于字符被输入到一段文字的时候
- compositionend:当文本段落的组成完成或取消时,compositionend 事件将被触发
合成事件在很多方面与输入事件很类似。在合成事件触发时,事件目标是接收文本的输入字段。唯
一增加的事件属性是 data,其中包含的值视情况而异:
- 在 compositionstart 事件中,包含正在编辑的文本(例如,已经选择了文本但还没替换);
- 在 compositionupdate 事件中,包含要插入的新字符;
- 在 compositionend 事件中,包含本次合成过程中输入的全部内容。
与文本事件类似,合成事件可以用来在必要时过滤输入内容。可以像下面这样使用合成事件:
<input id="myText" type="text"/><script>let textbox = document.getElementById("myText");textbox.addEventListener("compositionstart", (event) => {console.log('compositionstart', event.data);});textbox.addEventListener("compositionupdate", (event) => {console.log('compositionupdate', event.data);});textbox.addEventListener("compositionend", (event) => {console.log('compositionend', event.data);});</script>
三、合成事件的使用场景
需求背景:根据input输入的文字进行列表过滤。
需求实现
<template><div class="kh-idx"><input v-model="inputVal" type="text" /><ul class="kh-idx-ul"><li v-for="item in filteredList" :key="item" class="kh-idx-li">{{ item }}</li></ul></div>
</template><script lang="ts">
import { defineComponent, ref } from 'vue';export default defineComponent({name: 'KhIndex',setup() {return {inputVal: ref(''),list: ref(['爱与希望','花海','Mojito','最长的电影','爷爷泡的茶'])}},computed: {filteredList() {if (!this.inputVal) {return this.list;}return this.list.filter(item => item.indexOf(this.inputVal) > -1);}},
});
</script><style lang="less" scoped>
/** define kh-idx */
.kh-idx {&-ul {padding: 15px 8px;border: 1px solid #9c27b0;}&-li {padding: 4px 2px;border: 1px solid gainsboro;}
}
</style>
虽然能实现该功能,但是在使用中文进行输入时会有拼音到字符转变的时间,这段时间是没有办法实现过滤的。效果如下
为了优化该场景,可以使用compositionstart和compositionend相关事件进行优化。代码如下
<template><div class="kh-idx"><input v-model="inputVal"type="text"@compositionupdate="handleCompositionUpdate"@compositionend="handleCompositionEnd"/><ul class="kh-idx-ul"><li v-for="item in filteredList" :key="item" class="kh-idx-li">{{ item }}</li></ul></div>
</template><script lang="ts">
import { defineComponent, ref } from 'vue';export default defineComponent({name: 'KhIndex',setup() {let inputVal = ref('');const handleCompositionUpdate = (e: CompositionEvent) => {console.log('handleCompositionUpdate', e.data);inputVal.value = e.data;};const handleCompositionEnd = (e: CompositionEvent) => {console.log('handleCompositionEnd', e.data);inputVal.value = e.data;};return {inputVal,list: ref(['爱与希望','花海','Mojito','最长的电影','爷爷泡的茶']),handleCompositionUpdate,handleCompositionEnd}},computed: {filteredList() {if (!this.inputVal) {return this.list;}return this.list.filter(item => item.indexOf(this.inputVal) > -1);}},
});
</script><style lang="less" scoped>
/** define kh-idx */
.kh-idx {&-ul {padding: 15px 8px;border: 1px solid #9c27b0;}&-li {padding: 4px 2px;border: 1px solid gainsboro;}
}
</style>
四、san.js 中合成事件的应用
function elementOwnAttached() {if (this._rootNode) {return;}var isComponent = this.nodeType === NodeType.CMPT;var data = isComponent ? this.data : this.scope;/* eslint-disable no-redeclare */// 处理自身变化时双向绑定的逻辑var xProps = this.aNode._xp;for (var i = 0, l = xProps.length; i < l; i++) {var xProp = xProps[i];switch (xProp.name) {case 'value':switch (this.tagName) {case 'input':case 'textarea':if (isBrowser && window.CompositionEvent) {elementOnEl(this, 'change', inputOnCompositionEnd);elementOnEl(this, 'compositionstart', inputOnCompositionStart);elementOnEl(this, 'compositionend', inputOnCompositionEnd);}// #[begin] allua/* istanbul ignore else */if ('oninput' in this.el) {// #[end]elementOnEl(this, 'input', getInputXPropOutputer(this, xProp, data));// #[begin] allua}else {elementOnEl(this, 'focusin', getInputFocusXPropHandler(this, xProp, data));elementOnEl(this, 'focusout', getInputBlurXPropHandler(this));}// #[end]break;case 'select':elementOnEl(this, 'change', getXPropOutputer(this, xProp, data));break;}break;case 'checked':switch (this.tagName) {case 'input':switch (this.el.type) {case 'checkbox':case 'radio':elementOnEl(this, 'click', getXPropOutputer(this, xProp, data));}}break;}}var owner = isComponent ? this : this.owner;for (var i = 0, l = this.aNode.events.length; i < l; i++) {var eventBind = this.aNode.events[i];// #[begin] errorwarnEventListenMethod(eventBind, owner);// #[end]elementOnEl(this, eventBind.name,getEventListener(eventBind, owner, data, eventBind.modifier),eventBind.modifier.capture);}if (isComponent && this.nativeEvents) {for (var i = 0, l = this.nativeEvents.length; i < l; i++) {var eventBind = this.nativeEvents[i];// #[begin] errorwarnEventListenMethod(eventBind, this.owner);// #[end]elementOnEl(this, eventBind.name,getEventListener(eventBind, this.owner, this.scope),eventBind.modifier.capture);}}var transition = elementGetTransition(this);if (transition && transition.enter) {try {transition.enter(this.el, empty);}catch (e) {handleError(e, isComponent ? owner.parentComponent : owner, 'transitionEnter');}}
}
在上面的代码中,san对input 双向数据绑定时,监听了compositionstart和compositionend事件,然后两个事件处理函数也不是很麻烦,如下
/*** 双绑输入框CompositionEnd事件监听函数** @inner*/
function inputOnCompositionEnd() {if (!this.composing) { // 只有 composing 不为 1 时,后续才不执行return;}this.composing = 0;trigger(this, 'input'); // 触发 input 事件,trigger 函数中是自定义的 input 事件,这样用于定义的input事件就得意执行
}/*** 双绑输入框CompositionStart事件监听函数** @inner*/
function inputOnCompositionStart() {this.composing = 1; // composing 为 1 表示正在合成(composing)
}/*** 触发元素事件** @inner* @param {HTMLElement} el DOM元素* @param {string} eventName 事件名*/
function trigger(el, eventName) {var event = document.createEvent('HTMLEvents');event.initEvent(eventName, true, true);el.dispatchEvent(event); // 触发自定义事件
}
这样san就解决了使用input事件时,在输入汉字中没有出发input事件的情况。效果如下
参考文章
- js中compositionstart和compositionend事件
- javascript 高级程序设计(第四版)
- CompositionEvent