• 合成事件在san.js中的应用


    一、 什么是合成事件

    DOM3 Event 新增了合成事件(CompositionEvent ), 用于处理通常使用 IME 输入时的复杂输入序列。

    二、合成事件常见事件

    1. compositionstart:文本合成系统如 IME(即输入法编辑器)开始新的输入合成时会触发 compositionstart 事件。
    2. compositionupdate :事件触发于字符被输入到一段文字的时候
    3. 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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    请添加图片描述

    三、合成事件的使用场景

    需求背景:根据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>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    虽然能实现该功能,但是在使用中文进行输入时会有拼音到字符转变的时间,这段时间是没有办法实现过滤的。效果如下
    请添加图片描述
    为了优化该场景,可以使用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>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    请添加图片描述
    请添加图片描述

    四、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');
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103

    在上面的代码中,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); // 触发自定义事件
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    这样san就解决了使用input事件时,在输入汉字中没有出发input事件的情况。效果如下
    请添加图片描述
    参考文章

    1. js中compositionstart和compositionend事件
    2. javascript 高级程序设计(第四版)
    3. CompositionEvent
  • 相关阅读:
    代码随想录训练营day55
    重要文件怎么加密?文件加密软件哪个好用?
    常见CSS选择器
    Leetcode.309 买卖股票的最佳时机含冷冻期
    JAVA中解析package、import、class、this关键字
    【小沐学QT】QT学习之Web控件的使用
    【尚硅谷 Vue学习笔记】P5 hello小案例 P6分析Hello案例 P7模板语法 P8数据绑定
    Java中介者模式
    VSCode 的安装与插件配置
    嵌入式Ubuntu根文件系统移植带桌面
  • 原文地址:https://blog.csdn.net/qq_42683219/article/details/134480565