如何计算 textarea 光标的相对位置
原创
2020-5-27
22:46
编辑于
2022-6-22
09:02
哪些场景需要计算光标的相对位置?有个大家很常见的场景就需要,那就是输入框@人员,输入@后,需要在@字符旁显示人员列表,这时候就需要精确知道光标的相对位置。
textarea 并没有提供获取光标相对位置的方法,但提供了获取选区位置的属性(selectionStart,selectionEnd),没有选中文字时,通过selectionStart 就可以知道当前光标在字符串(即textarea的value值)中的位置。
通过 selectionStart 似乎也没有什么好的方法能快速计算出光标的相对位置,如果把光标变成一个 html 元素,似乎就很简单了。
要把光标变成一个 html 元素,就需要模拟一个和 textarea 一样的容器,容器所有样式和 textarea 保持一致,包括换行,文字大小,行间距等,不然计算出来的光标相对位置就不准确了。
<style>
.textarea,
textarea {
padding: 10px;
font-family: inherit;
width: 200px;
height: 100px;
resize: none;
font-size: 14px;
line-height: 1;
border: 1px solid #ddd;
white-space: pre-wrap;
word-break: break-all;
}
</style>
<textarea>前端路迹
qinshenxue.com
</textarea>
<div class="textarea">前端路迹
qinshenxue.com</div>
可以看到上面两个元素样式完成一致,展示效果如下。
接下来在 div.textarea 中插入一个html元素来模拟光标。
<style>
.cursor{
position: absolute;
border-left: 1px solid #000;
}
</style>
<div class="textarea">前端路迹<span class="cursor"> </span>
qinshenxue.com</div>
效果如下。
可以看到,光标的位置和 textarea 中的光标位置是一致的。接下来要做的就是利用 selectionStart 来动态插入光标元素,具体步骤如下:
- textarea 的 selectionStart 和 value 值
- 将 value 按 selectionStart 截取为两个字符串,分别创建两个 TextNode
- 创建一个 html 元素 span.cursor
- 将上面三个 html 元素插入到到 div.textarea 中
- 获取 span.cursor 的 offsetLeft 和 offsetTop
以上步骤对应代码实现如下。
var textarea = document.querySelector('textarea')
textarea.oninput = function (e) {
var value = textarea.value
var selectionStart = textarea.selectionStart
var str1 = value.slice(0, selectionStart)
var str2 = value.slice(selectionStart)
var textNode1 = document.createTextNode(str1)
var textNode2 = document.createTextNode(str2)
var cursor = document.createElement('span')
cursor.innerHTML = ' '
cursor.setAttribute('class','cursor')
var mirror = document.querySelector('.textarea')
mirror.innerHTML = ''
mirror.appendChild(textNode1)
mirror.appendChild(cursor)
mirror.appendChild(textNode2)
console.log(cursor.offsetLeft,cursor.offsetTop)
}
演示效果如下。
以上是一个原理的简单示例,详细的实现时还需要注意以下几点:
- selectionStart 是兼容 IE9+ 的,但是在 IE 下,textarea 默认显示了滚动条区域,即使内容还没有溢出,宽度和下面 div.textarea 不是一致的,会导致计算误差,需要通过 CSS 解决。
- 下面计算光标的容器一般是隐藏的,而且应该绝对定位在 textarea 层下,保持和 textarea 大小一致,这样获取的 offsetTop,offsetLeft 才是一致的。
- 两者的滚动条位置要保持一致。
- 计算位置可以考虑使用 getBoundingClientRect 更简单。

关注我的公众号