很長的前言
前端時間需要做一個input元件,元件要求之一是:動態計算input文字並展示出來,大概如下:
本來就是基於vue來做,想起來也是很簡單的事情,就是獲取$input.value.length
~確實也是如此。
最初我是這麼寫的:
<template>
<div class="self-input">
<input
ref="input"
type="text"
class="self-input__inner"
maxlength="maxlength"
:input="handleInput"
:value="currentValue"
>
<span class="self-input__calculator">
<span class="self-input__calculator--current">{{ textCount }}</span>
<span class="self-input__calculator--max">{{ maxlength }}</span>
</span>
</div>
</template>
<script>
export default {
name: `SelfInput`,
props: {
value: { type: [String, Number] },
maxlength: [String, Number]
},
data() {
return {
currentValue: this.value,
textCount: 0
};
},
methods: {
handleInput() {
let inputVal = this.$refs.input.value;
inputVal = inputVal.trim();
this.textCount = inputVal.length;
this.$emit(`input`, inputVal);
}
}
};
</script>
<style lang="scss">
.self-input{
$--calculator-width: 56px;
&{ position: relative;display: inline-block; }
&__inner{ padding-left: 5px;line-height: 28px;width: 220px;padding-right: $--calculator-width;font-size: 14px; }
&__calculator{
&{ position: absolute;top: 0;right: 0;bottom: 0;display: inline-flex;justify-content: flex-end;align-items: center;padding-right: 5px;width: $--calculator-width;box-sizing: border-box; }
&--current{
&::after{ content: "/";display: inline-block; }
}
&--max{ color: gray; }
}
}
</style>
複製程式碼
如你所見,在中文輸入時出現了非預期的文字數目計數(附上 Demo1 )~
正文
看win下的效果就知道,在使用中文輸入的時候,直接使用input
事件(v-model語法糖就是監聽input事件)就出現了非預期的問題!
在看ElementUI原始碼的時候發現了一個以前沒有用過的事件(見識短淺),composition事件。這個事件有三個事件組成,分別是:compositionstart、compositionupdate和compositionend。
compositionstart
事件觸發於一段文字的輸入之前(類似於 keydown 事件,但是該事件僅在若干可見字元的輸入之前,而這些可見字元的輸入可能需要一連串的鍵盤操作、語音識別或者點選輸入法的備選詞)。
compositionupdate
事件觸發於字元被輸入到一段文字的時候(這些可見字元的輸入可能需要一連串的鍵盤操作、語音識別或者點選輸入法的備選詞)
當文字段落的組成完成或取消時,
compositionend
事件將被觸發 (具有特殊字元的觸發, 需要一系列鍵和其他輸入, 如語音識別或移動中的字詞建議)。
轉成凡人(說的就是自己)聽得懂的話就是:在一開始中文輸入的時候會觸發compositionstart
事件,當繼續中文輸入但未選詞前會持續觸發compositionupdate
事件,然後當選詞後則觸發compositionend
事件(針對當前情況如是說)!
用input事件配合以上三事件優化文字計數器
<input
class="self-input__inner"
type="text"
:value="exValue"
@compositionend="handleComposition"
@input="handleInput"
maxlength="maxlength"
>
<!-- ... -->
<script>
export default {
// ...
data() {
return {
// ...
isOnComposition: false
};
},
methods: {
_setTextCount(val) {
this.textCount = val.length || 0;
},
handleComposition(event) {
this.isOnComposition = event.type !== `compositionend`;
!this.isOnComposition && this.handleInput(event);
},
handleInput(event) {
let value;
if (this.isOnComposition) return;
value = event.target.value;
this.currentValue = value;
this._setTextCount(value);
this.$emit(`input`, value);
}
}
// ...
}
</script>
複製程式碼
這裡只是用到了Composition事件之一, compositionend
。其實要做得更加健壯應該三個事件都用到!這裡的思路也很簡單,如果是非中文輸入的時候就不會觸發compositionend
事件,正常執行input事件的回撥,而中文輸入的時候,compositionend
和input
都會同時觸發,在未完成選詞前,當然是不能執行input回到的真正邏輯,因此加入一個isOnComposition
狀態記錄當前是否進行中文輸入,input回撥函式則據此判斷是否執行真正的業務邏輯!
附上jsfiddle原始碼Demo: Demo2
更加簡單的做法
<!-- ... -->
<input
class="self-input__inner"
type="text"
v-model="currentValue"
:maxlength="maxlength"
/>
<!-- ... -->
<script>
// ...
data() {
return {
currentValue: (this.value === undefined || this.value === null) ? `` : this.value
};
},
computed: {
textCount() {
return this.currentValue.length || 0;
}
}
// ...
</script>
複製程式碼
對比上一種做法,將v-bind
繫結的value
屬性,換成v-model
,其餘去掉的部分一看到~,基本就是做了這麼一點功夫就搞定了上一種做法的長篇大論~
附上jsfiddle原始碼Demo: 另一種簡單的實現
最後水一句
本來就不是常用的事件,寫個blog和小demo記錄下,免得下次……
覺得寫得還可以,移步Github Blog鼓勵下,哪怕一點點評論和like,讓我知道我寫的東西還是有點用都是很大的激勵~