如何计算 textarea 光标的相对位置

2020/5/27

哪些场景需要计算光标的相对位置?有个大家很常见的场景就需要,那就是输入框@人员,输入@后,需要在@字符旁显示人员列表,这时候就需要精确知道光标的相对位置。

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">&nbsp;</span>
        qinshenxue.com</div>

效果如下。

可以看到,光标的位置和 textarea 中的光标位置是一致的。接下来要做的就是利用 selectionStart 来动态插入光标元素,具体步骤如下:
获取 textarea 的 selectionStart 和 value 值

  1. 将 value 按 selectionStart 截取为两个字符串,分别创建两个 TextNode
  2. 创建一个 html 元素 span.cursor
  3. 将上面三个 html 元素插入到到 div.textarea 中
  4. 获取 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 = '&nbsp;'
    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)
}

演示效果如下。

  1. 以上是一个原理的简单示例,详细的实现时还需要注意以下几点:
  2. selectionStart 是兼容 IE9+ 的,但是在 IE 下,textarea 默认显示了滚动条区域,即使内容还没有溢出,宽度和下面 div.textarea 不是一致的,会导致计算误差,需要通过 CSS 解决。
  3. 下面计算光标的容器一般是隐藏的,而且应该绝对定位在 textarea 层下,保持和 textarea 大小一致,这样获取的 offsetTop,offsetLeft 才是一致的。
  4. 两者的滚动条位置要保持一致。
  5. 计算位置可以考虑使用 getBoundingClientRect 更简单。
原创文章,持续完善中,转载请注明出处。本文地址: https://www.qinshenxue.com/article/how-to-calc-textarea-cursor-position.html