vue 实现在文本框光标处插入内容
场景:封装一个组件 - 简易计算器,在文本框点击时显示计算器,点击计算器上的按钮即数字、运算符等,就将点击的按钮文本插入到文本框的光标处
计算器组件calculator.vue
<template> <!-- 计算器 --> <div ref="calculator" class="calculator" :data-value="value"> <div class="texts"> <span v-for="(item, index) in calculatorTexts" :key="index" @click="handleTextClick(item)"> {{ item }} </span> </div> <div class="close" @click.stop="handleClose"> <a-icon type="close-circle" /> </div> <!-- <div class="operator"> <span>确定</span> <span>取消</span> </div> --> </div> </template> <script> export default { name: 'Calculator', props: { initValue: { type: String, require: true, default: '' } }, data() { return { // 计算器文本数组 calculatorTexts: (() => { let texts = [] for (let index = 0; index < 10; index++) { texts.push(index.toString()) } texts = texts.concat(['.', '+', '-', '*', '/', '(', ')']) return texts })(), // 计算器是否可见 calculatorVisible: true, // 样式 style: { top: '0px', left: '0px' }, // 光标是否处于计算器内 calculatorFocus: false, // 文本值 value: this.initValue } }, mounted() { }, methods: { // 文本点击 handleTextClick(text) { // 累积文本 或 覆盖文本 this.value = text this.$emit('updateCalculatorValue', this.value) }, // 关闭计算器 handleClose() { this.$refs.calculator.style.top = '0px' this.$refs.calculator.style.left = '0px' this.$refs.calculator.style.display = 'none' this.value = '' }, // 设置坐标 setLocatioin(point) { Object.assign(this.style, point) } } } </script> <style lang="less" scoped> .calculator { display: none; width: 122px; height: auto; border: 1px #f0f0f0 solid; position: absolute; top: 0; left: 0; z-index: 9999; background-color: white; -moz-user-select: none; -ms-user-select: none; user-select: none; .texts { display: flex; flex-wrap: wrap; span { flex-shrink: 0; display: inline-block; width: 30px; border-right: 1px #f0f0f0 solid; border-bottom: 1px #f0f0f0 solid; text-align: center; padding: 5px 10px; &:hover { color: orange; border-bottom: 1px orange solid; cursor: pointer; } } } .operator { display: flex; justify-content: center; span { display: inline-block; border: 1px #f0f0f0 solid; text-align: center; padding: 5px 10px; margin: 8px 5px; &:hover { color: orange; border-bottom: 1px orange solid; cursor: pointer; } } } .close { display: inline-block; position: absolute; top: -22px; right: -15px; width: auto; font-size: 22px; cursor: pointer; &:hover { color: red; } } } </style>
父组件template
<a-form-model-item label="姓名" prop="name"> <a-input ref="name" id="name" v-model="form.name" placeholder="请输入姓名" :max-length="20" @click="(event) => handleShowCalculator(event, 'calculator1', 'name')" /> <calculator ref="calculator1" @updateCalculatorValue="handleUpdateCalculatorValue" style="top: 44px;"></calculator> </a-form-model-item>
父组件script
// 显示计算器 handleShowCalculator (event, calculatorRef, field) { this.$refs[calculatorRef].$el.style.display = 'block' event.target.setAttribute('field', field) }
// 计算器文本点击回调 handleUpdateCalculatorValue (val) { const target = this.$refs.name.$el const pos = this.getCursorPosition(target) const frontStr = this.form.name.substring(0, pos) const behindStr = this.form.name.substring(pos, this.form.name.length) this.form.name = frontStr + val + behindStr /// 注意,定位光标需要在 Vue 数据下一次更新之后,两种方式: /// 方法1:将 handleUpdateCalculatorValue 函数变为异步函数,方法前加上 async,然后在光标定位代码 this.setCaretPosition(target, pos + val.length) 的前面加上等待数据更新后的代码 await this.$nextTick() /// 方法2:将光标定位代码 this.setCaretPosition(target, pos + val.length) 写在 this.$nextTick() 中,即 this.$nextTick(() => { this.setCaretPosition(target, pos + val.length) }) this.$nextTick(() => { this.setCaretPosition(target, pos + val.length) }) }
封装的两个操作光标方法:
// 获取光标位置 getCursorPosition (el) { let pos = 0 if ('selectionStart' in el) { pos = el.selectionStart } else if ('selection' in document) { el.focus() const selRange = document.selection.createRange() const selRangeLength = document.selection.createRange().text.length selRange.moveStart('character', -el.value.length) pos = selRange.text.length - selRangeLength } return pos }, // 设置光标位置 setCaretPosition (el, pos) { if (el.setSelectionRange) { el.focus() el.setSelectionRange(pos, pos) } else if (el.createTextRange) { const range = el.createTextRange() range.collapse(true) range.moveEnd('character', pos) range.moveStart('character', pos) range.select() } },
两个光标操作方法基于以下代码封装,也可以使用下面的代码,二选一,同样要注意,定位光标需要在 Vue 数据下一次更新之后:
// IE浏览器 if (document.selection) { target.focus() const sel = document.selection.createRange() sel.text = val } else if (target.selectionStart) { // 谷歌 Firefox 等 const startPos = target.selectionStart const endPos = target.selectionEnd const restoreTop = target.scrollTop // 获取滚动条高度 // 拼接字符 this.form.name = this.form.name.substring(0, startPos) + val + this.form.name.substring(endPos, this.form.name.length) if (restoreTop > 0) { target.scrollTop = restoreTop } target.focus() target.selectionStart = startPos + val.length target.selectionEnd = startPos + val.length } else { this.form.name += val target.focus() }
界面效果
在光标处插入字符
嘴角上扬,记得微笑