阅读 418

div contenteditable属性下限制性输入以及自定义光标位置

背景

项目采用vue+Ant Design of Vue,需要在div属性contenteditable为true的实现自定义输入; 具体需求:

  • 键盘只能输入0~9,/*-+.

  • 通过不同按钮可以插入不同设备名

  • 点击不同地方,根据光标位置删除或添加数据

  • 最后将输入字符串的中的设备名替换成对应编码值

效果图如下 image.png

实现过程

思路

实现需求的关键点

  1. **如何获取数据?如何便捷编辑数据?**从键盘和按钮点击事件监控每一次输入,并将改变的值存储在数组中,方便增删改查.

  2. **如何获取光标位置,以及改变光标位置?**根据Selection对象获取光标位置,以及通过collapse方法改变光标位置.

  3. **如何根据光标位置修改对应数组数据?**新建一个数组储存光标位置与实际数组数据之间的映射关系.例如右侧的按钮组,一个按钮代表一个值.例如点击电量1,在输入框内输入三个字符'电量1'(光标会记录3个位置),但在数据数组上直接储存一个值(数组长度加1),这时候光标位置和对应的数组值的下标会产生偏移.所以需要记录两者的映射关系,用于修改对应数据.

具体代码

<template>   <div>     <a-card>       <a-row>         <a-col :span="16">           <div>             <div>               <a-button                 type="primary"                 v-for="item in btnList"                 :key="item"                 style="margin-right: 10px"                 @click="setDataList(item, 0)"               >                 {{ item }}               </a-button>             </div>             <div>               <a-button @click="onExport" style="margin-right: 10px"> 导出表达式 </a-button>               <a-button style="margin-right: 10px"> 清空 </a-button>             </div>           </div>         </a-col>         <a-col :span="8">           <a-input-search placeholder="input search text" enter-button="查询" size="large" @search="onSearch" />         </a-col>       </a-row>       <a-row>         <a-col :span="16" style="padding: 10px">           <div             ref="editor"             contenteditable="true"             @blur="onblur"             @click="onclick"             @focus="onfocus"             @input="oninput"           ></div>         </a-col>         <a-col :span="8">           <a-button             v-for="item in textList"             :key="item"             style="margin-right: 10px; margin-top: 10px"             @click="setDataList(item, 1)"             >{{ item }}</a-button           >         </a-col>       </a-row>     </a-card>   </div> </template> <script> const btnList = ['+', '-', '*', '/', '(', ')', 'DEL'] const textList = ['电量1', '电量2', '电量3'] const correspondingTable = {   '+': '+',   '-': '-',   '*': '*',   '/': '/',   '(': '(',   ')': ')',   '.': '.',   电量1: '#elect1#',   电量2: '#elect2#',   电量3: '#elect3#',   0: 0,   1: 1,   2: 2,   3: 3,   4: 4,   5: 5,   6: 6,   7: 7,   8: 8,   9: 9, } export default {   data() {     return {       btnList,       textList,       correspondingTable,       // 真实数据位置       realDomKeys: [],       // 储存对应的字符串值       dataList: [],       // 光标位置       focusOffset: 0,       // 定义最后光标对象       lastEditRange: null,     }   },   mounted() {},   methods: {     setDataList(val, type) {       // 记录数据存储位置(指的是数据所在数组位置的下标值)       let cursorJS       // 记录光标存在位置(指的是当前光标前的数据个数)       let cursorDom = this.focusOffset       // 判断光标所处位置,如果在中文体内,则放到中文右括号侧,其余情况确认光标的真实指向       // 光标是否位于最末尾       if (cursorDom < this.realDomKeys.length && cursorDom > 0) {         // 判断光标是否在中文体内,如果在就让光标落到此中文最右边(右括号侧         if (this.realDomKeys[cursorDom] instanceof Object) {           //  在中文体内           // 记录光标应该在的位置和对应此刻数据存储位置           if (cursorDom == this.realDomKeys[cursorDom].start) {             // console.log('@@光标在中文体旁边')           } else {             cursorDom = this.realDomKeys[cursorDom].start + this.realDomKeys[cursorDom].n             // console.log('@@光标在中文体内')           }           cursorJS = this.getCursorJS(cursorDom) - 1 //取的是当前指向数据的下标值         } else {           //  不在中文体内           cursorJS = this.getCursorJS(cursorDom) - 1           // console.log('@@光标在在中文体外,且在数组内,真实数据位置为', cursorJS)         }       } else if (cursorDom == this.realDomKeys.length) {         // 位于最末尾         cursorJS = this.dataList.length - 1       } else if (cursorDom == 0) {         // 位于最前端         cursorJS = -1       }       // 增减datalist数据       if (val instanceof Object) {         // this.dataList.push(val.name)       } else if (val == 'DEL') {         // 删除数据         let str         if (cursorJS != -1) {           //如果在最前端刪除無效,即cursorJS=-1和0           str = this.dataList[cursorJS]           this.dataList.splice(cursorJS, 1)           this.setRealDomKeys()           if (/.*[\u4e00-\u9fa5]+.*$/.test(str) || str.length > 1) {             cursorDom = cursorDom - str.length - 2           } else {             cursorDom--           }         }       } else {         //添加数据         this.dataList.splice(cursorJS == -1 ? 0 : cursorJS + 1, 0, val)         this.setRealDomKeys()         if (type) {           cursorDom += val.length + 2         } else {           cursorDom++         }       }       this.focusOffset = cursorDom       this.keepLastIndex(this.$refs.editor)     },     // 重新计算dataList的对应realDomKeys     setRealDomKeys() {       this.realDomKeys = []       this.dataList.forEach((item, index) => {         //判断是否为设备名         if (/.*[\u4e00-\u9fa5]+.*$/.test(item) || item.length > 1) {           //凡是包含中文,以及字符串长度大于1的都默认为设备名           let len = item.length + 2           let i = 0           let reaLen = this.realDomKeys.length           while (i < len) {             this.realDomKeys.push({               index: index, //对应数据数组的下标值               start: reaLen, //此数据在realDomKeys起始下标               n: len, //共占有多少数据格             })             i++           }         } else {           this.realDomKeys.push(index)         }       })     },     // 获取当光标不在中文体内时,对应的数据位置     getCursorJS(cursorDom) {       let count = 0       let i = 0       while (i < cursorDom) {         if (this.realDomKeys[i] instanceof Object) {           count++           i += this.realDomKeys[i].n         } else {           count++           i++         }       }       return count     },     // 检查输入是否是数组或者+-/*.0-9     oninput(e) {       var text       if (!e.data) {         this.setDataList('DEL', 0)       } else {         text = e.data.replace(/[^\d\/\*\-\+\.]/g, '')       }       if (text) {         this.setDataList(text, 0)       }else{         this.keepLastIndex(this.$refs.editor)       }            },     keepLastIndex(obj) {       if (window.getSelection) {         obj.focus()         // 获取选定对象         var selection = getSelection()         if (this.lastEditRange) {           // 存在最后光标对象,选定对象清除所有光标并添加最后光标还原之前的状态           selection.removeAllRanges()           selection.addRange(this.lastEditRange)         }         obj.innerHTML = this.getHtml()         selection.collapse(selection.anchorNode.childNodes[0], this.focusOffset)       }     },     // 将存储数据转化成html     getHtml() {       let str = ''       this.dataList.forEach((item) => {         if (/.*[\u4e00-\u9fa5]+.*$/.test(item) || item.length > 1) {           str += `(${item})`         } else {           str += item         }       })       return str     },     // 點擊editor時獲取光標位置     onclick(e) {       let selection = window.getSelection()       this.lastEditRange = selection.getRangeAt(0)       this.focusOffset = selection.focusOffset     },     // 将存储数据转化成後端字符串形式     onExport() {       let str = ''       this.dataList.forEach((item) => {         if (/.*[\u4e00-\u9fa5]+.*$/.test(item) || item.length > 1) {           str += this.correspondingTable[item]         } else {           str += item         }       })       console.log(str,JSON.stringify(this.dataList))     },     onblur() {       console.log('onblur')     },     onfocus() {       // console.log('onfocus', window.getSelection(), document.getElementById('editor'))     },     onSearch(value) {       console.log(value)     },   }, } </script> <style scoped> .box {   border-radius: 5px;   border: 2px solid #000;   height: 300px;   padding: 5px; } .btn-box {   width: 100%;   display: flex;   justify-content: space-between;   align-items: center; } .text {   font-weight: 600;   color: aquamarine; } </style> 复制代码

重点分析

设置光标位置核心代码.将div内文本字符串看作一个选区,传入collapse方法中,通过设置落在节点的偏移量来改变光标位置

keepLastIndex(obj) {       if (window.getSelection) {         obj.focus()         // 获取选定对象         var selection = getSelection()         if (this.lastEditRange) {           // 存在最后光标对象,选定对象清除所有光标并添加最后光标还原之前的状态           selection.removeAllRanges()           selection.addRange(this.lastEditRange)         }         obj.innerHTML = this.getHtml()         selection.collapse(selection.anchorNode.childNodes[0], this.focusOffset)       }     }, 复制代码

通过监测input( @input="oninput")输入,用正则判断输入的值是否为理想值,如果是则将值存储到数组中,反之不存储.

 // 检查输入是否是数组或者+-/*.0-9     oninput(e) {       var text       if (!e.data) {         this.setDataList('DEL', 0)       } else {         text = e.data.replace(/[^\d\/\*\-\+\.]/g, '')       }       if (text) {         this.setDataList(text, 0)       }else{         this.keepLastIndex(this.$refs.editor)       }            },


作者:流码
链接:https://juejin.cn/post/7038884730506313741

文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐