Composition實現科學文字計數器

issaxite發表於2019-03-03

很長的前言

前端時間需要做一個input元件,元件要求之一是:動態計算input文字並展示出來,大概如下:

Composition實現科學文字計數器

本來就是基於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>
複製程式碼
Composition實現科學文字計數器
Composition實現科學文字計數器

如你所見,在中文輸入時出現了非預期的文字數目計數(附上 Demo1 )~

正文

看win下的效果就知道,在使用中文輸入的時候,直接使用input事件(v-model語法糖就是監聽input事件)就出現了非預期的問題!

在看ElementUI原始碼的時候發現了一個以前沒有用過的事件(見識短淺),composition事件。這個事件有三個事件組成,分別是:compositionstartcompositionupdatecompositionend

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事件的回撥,而中文輸入的時候,compositionendinput都會同時觸發,在未完成選詞前,當然是不能執行input回到的真正邏輯,因此加入一個isOnComposition狀態記錄當前是否進行中文輸入,input回撥函式則據此判斷是否執行真正的業務邏輯!
附上jsfiddle原始碼Demo: Demo2

Composition實現科學文字計數器
Composition實現科學文字計數器
Composition實現科學文字計數器

更加簡單的做法

<!-- ... -->
<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,讓我知道我寫的東西還是有點用都是很大的激勵~

相關文章