一、 什么是合成事件
DOM3 Event 新增了合成事件(CompositionEvent ), 用于处理通常使用 IME 输入时的复杂输入序列。
二、合成事件常见事件
- compositionstart:文本合成系统如 IME(即输入法编辑器)开始新的输入合成时会触发 compositionstart 事件。
- compositionupdate :事件触发于字符被输入到一段文字的时候
- compositionend:当文本段落的组成完成或取消时,compositionend 事件将被触发
合成事件在很多方面与输入事件很类似。在合成事件触发时,事件目标是接收文本的输入字段。唯
一增加的事件属性是 data,其中包含的值视情况而异:
- 在 compositionstart 事件中,包含正在编辑的文本(例如,已经选择了文本但还没替换);
- 在 compositionupdate 事件中,包含要插入的新字符;
- 在 compositionend 事件中,包含本次合成过程中输入的全部内容。
与文本事件类似,合成事件可以用来在必要时过滤输入内容。可以像下面这样使用合成事件:
<input id="myText" type="text"/>
<script>![请添加图片描述](https://img-blog.csdnimg.cn/481e6c9238ad45dcaabedfb9adcbff26.png)
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] error
warnEventListenMethod(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] error
warnEventListenMethod(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