如何获取Input中光标的位置?

一、背景

测试提出气泡要能跟随光标的位置移动

二、气泡方案

旧方案

input 上波浪线由覆盖在 input 上面的 span 显示,span 绑定点击事件,显示对应的气泡。
问题:

  • 点击 span 后光标在 input 中的位置需要额外计算,容易出错
  • 无法拖动选中

新方案

  • 波浪线方案保持一致,但不覆盖 input,只作为样式存在,不关心事件
  • 使用 input 特有的属性 selectionStart,兼容性表现也很好
  • 复用波浪线的位置数据,代替通过 span 的点击事件确定位置

三、实现

确定了新方案之后,主要的问题就变成了解决如何获取光标所在的位置。
想要获取光标相对于屏幕的位置信息,大概可以分三步走:

  1. input 相对于屏幕的位置
  2. 光标相对于 input 的位置
  3. 累加位置信息的得出光标相对屏幕的位置

难点主要在第二步,如何获取光标相对于屏幕的位置。
其实我们反过来想一下,获取光标位置 ≈ 获取光标后面的元素的位置信息(为什么是 ≈,后面会解释)
这个过程大概也可以分为三步走

  1. mock 出一个于 input 一模一样 DOM
  2. 根据 selectionStart 的信息把 input 文字分为两部分
  3. 获取后一部分的元素的位置信息即为光标相对于 input 的位置

有了思路之后,就可以着手实现了:

  • 为了方便可以直接获取 ​getComputedStyle().cssText​,作为 mock inputstyle,当然最好是获取和位置相关的 css 就好。
  • mock 两个 dom,储存 input 的文字信息。
  • 根据 selectionStartinput 截断为两段,像这样:

最后就可以通过 offset 来获取位置信息

1
2
3
4
5
{
left: mockSpanNext.offsetLeft + parseInt(originStyle.getPropertyValue('border-left-width'), 10),
top: mockSpanNext.offsetTop + parseInt(originStyle.getPropertyValue('border-top-width'), 10),
bottom: mockSpanNext.offsetTop + lineHeight
}

四、问题


对于这种情况,1,2 的位置 selectionStart 是一样的,按照我们上面的方法,我们只能获取到 2 的位置信息。这也是目前没有解决的问题之一(也是 ≈ 的原因)。
大家有什么好的方案欢迎提出探讨。

不对之处欢迎指正,附上 demo 地址:codepen

相关资料:

textarea-caret-position