前言

在输入编辑的业务场景中,可能会需要在光标当前的位置或附近显示提示选项。
比如社交评论中的@user功能,要确保提示的用户列表总是出现在@字符右下方,又或者是在自定义编辑器中 autocomplete 语法提示,都需要获取光标当前的位置作为参照点。

两种位置

对于 WEB 开发来讲,当我们提到某某元素的位置,通常是指这个元素相对于父级或文档的像素单位坐标。而对于输入框中光标,就有了额外的区分。

相对于内容

相对于内容,光标位于第几个字符之后,姑且称之为字符位置吧。

相对于UI

相对于UI,也就是跟普通页面元素一样的像素位置了。

插入或替换内容

在前言提到的场景中,也有在光标位置处插入内容的需求,比如对选取文字加粗text => <strong>text</strong>等。

textarea

textarea元素可以很容易获取到选择的一段文字的起止位置。如果当前没有选择文字,则两个位置值都为光标右侧字符的索引,从 0 开始。

// 开始位置
textarea.selectionStart
// 结束位置
textarea.selectionEnd

对于加粗功能,有了起止位置,就能获取到选择的文字内容,然后对内容进行替换。
由于textarea不能包含子元素,只有纯文本,所以基于textarea实现加粗只能像用 Markdown 标记语法实现。

var selectedText = textarea.value.substring(textarea.selectionStart, textarea.selectionEnd)
textarea.setRangeText('**'+ selectedText +'**')

textarea.setRangeText(text: String) 把选中的文字替换为其他内容。

contenteditable

也可能我们会使用contenteditable属性把一个元素变为可编辑元素。而上面所用的属性和函数都是普通元素所没有的,所以要换一种姿势实现。

还是以加粗功能为例。

// 获取文档中选中区域
var range = window.getSelection().getRangeAt(0)
var strongNode = document.createElement('strong')
// 选中区域文本
strongNode.innerHTML = range.toString()
// 删除选中区
range.deleteContents()
// 在光标处插入新节点
range.insertNode(strongNode)

基于contenteditable的可编辑元素,其中的内容均为子元素,文本为textNode,加粗使用 HTML 元素,插入或替换是对元素的操作。

如果想使用操作内容的思路实现会比较麻烦,因为可以获取到的起止位置是基于子元素的。

<div contenteditable>hello<strong>你好</strong><big>w</big>orld</div>

假如选中的文字是你好wor,调用相关 API 的输出如下。

// 当前在文档中选择的文本,document 和 window 都有这个函数
// var selection = document.getSelection()
var selection = window.getSelection()
selection.anchorNode // 你好
selection.anchorOffset // 0
selection.focusNode // orld
selection.focusOffset // 2
 
// 或者使用 Range
var range = selection.getRangeAt(0)
range.startContainer // 你好
range.startOffset // 0
range.endContainer // orld
range.endOffset // 2

最终可以获取到起止元素以及选中区域在开始元素内容中的字符位置和在结束元素内容中的字符位置。
其中的起止元素均为textNode类型,通过parentNode获取到包裹元素。

range.startContainer.parentNode // <strong>你好</strong>
range.endContainer.parentNode // <div contenteditable>...</div>

需要注意的是通过SelectionRang获取到起止位置是有方向之分的,从左向右选择和从右向左选择得到的值是正好相反的。

基于光标像素位置创建内容

这里就要开始用像素位置,同样分为两种实现来讲。

contenteditable

可编辑元素获取光标像素位置就像textarea获取光标的字符位置一样简单。

var range = window.getSelection().getRangeAt(0)
range.getBoundingClientRect() // { width, height, top, right, bottom, right }

这么具体的尺寸值,实现自动完成真是 So easy!

textarea

textarea其中的内容都是纯文本,在 DOM 中不存在相关的对象,对于像素位置就得另作他想了。

基于行高和字体大小计算

// 1.获取光标结束位置
var end = textarea.selectionEnd
// 2.通过匹配光标之前文本中的换行符计算所在行
var row = textarea.value.substring(0, end).match(/\r\n|\r|\n/).length
// 3.计算 top,行高 * 行数 + 上填充 + 边框宽度
var top = lineHeight * (row + 1) + paddingTop + borderWidth
// 4.获取光标左侧的文本
var leftText = textarea.value.split(/\r\n|\r|\n/)[row]
// 5.影响一段文字所占宽度的因素太多,除字体大小、中英文、符号、字符间距等,还有字体、浏览器、系统等客观因素
// var left = ...

这个方案的思路是没问题的,但是考虑所有问题的成本太高。
虽然可以创建测试元素去计算文本宽度,但这个方案本身是从严谨的角度出发的。与其混在一块,直接用取巧的办法更简单.

镜像元素

文本不支持定位?那我创建 DOM 好了。

// 光标位置
var end = textarea.selectionEnd
// 光标前的内容
var beforeText = textarea.value.slice(0, end)
// 光标后的内容
var afterText = textarea.value.slice(end)
// 对影响 UI 的特殊元素编码
var escape = function(text) {
return text.replace(/<|>|`|"|&/g, '?').replace(/\r\n|\r|\n/g, '<br>')
}
// 创建镜像内容,复制样式
var mirror = '<div class="'+ textarea.className +'">'
+ escape(beforeText)
+ '<span id="cursor">|</span>'
+ escape(afterText)
+ '</div>'
// 添加到 textarea 同级,注意设置定位及 zIndex,使两个元素重合
textarea.insertAdjacentHTML('afterend', mirror)
// 通过镜像元素中的假光标占位元素获取像素位置
var cursor = document.getElementById('cursor')
cursor.getBoundingClientRect() // { width, height, top, right, bottom, right }

End

最后悄悄说一句,以上内容不兼容低版本 IE,但是 IE 毕竟主场运行,有些 API 反而是其他浏览器所没有的。就上面提到的案例来说,低版本 IE 也有对应的 API 可用。
真是不想在 IE 上去浪费精力了,索性不提。

原文路径:https://imys.net/20161125/cursor-offset-at-input.html

JavaScript 获取输入时的光标位置及场景问题的更多相关文章

  1. js获取输入框中当前光标位置并在此位置插入字符串的方法(angularjs+ts)

    一半是参照别人代码,一半是自己代码,略笨拙,如果有更好的方法希望分享. 获取当前光标位置的方法 getCaretPosition (obj:any) { //获取输入框中当前光标的位置,obj为此输入 ...

  2. Javascript 获取窗口的大小和位置

    在Javascript中可以使用OuterWidth,OuterHeight 获取浏览器的大小.用 innerWidth,innerHeight 来获取窗口的大小(除去浏览器边框部分).对于IE6 及 ...

  3. javascript 获取滚动条距离顶部的位置(兼容所有的)。

    function getScrollTop() { var scrollPos; if (window.pageYOffset) { scrollPos = window.pageYOffset; } ...

  4. 用JQuery获取输入框中的光标位置

    (function ($, undefined) { $.fn.getCursorPosition = function () { var el = $(this).get(0); var pos = ...

  5. js获取可编辑区域光标位置

    请到简书中看,地址: http://www.jianshu.com/p/19a507cd5fd7 github测试例子 https://github.com/Stevenzwzhai/plugs/tr ...

  6. input输入时光标位置靠上问题解决

    在css中如果我们定义了input高度在输入时会发现光标位置靠上了不在居中了,在Chrome浏览器中,当设置了line-height时,input无文字,光标高度与line-height一致:inpu ...

  7. C#-WinForm-如何获取文本框(TextBox)中鼠标,光标位置

    文本框(TextBox)中的鼠标位置和光标位置是两个不同的概念,鼠标位置是要点击鼠标后(NouseDown)获取到,而光标位置却是实时就要获取到,也就是用户输入一个字符(KeyUp),这个位置就要改变 ...

  8. javascript获取以及设置光标位置

    一. 获取光标位置: // 获取光标位置 function getCursortPosition (textDom) { var cursorPos = 0; if (document.selecti ...

  9. Javascript实例技巧精选(7)—设置和获取文本框与文本域的光标位置(兼容IE和Chrome,Firefox)

    >>点击这里下载完整html源码<< 截图如下: 本实例描述了如何用Javascript来控制和获取文本框/文本域的鼠标光标位置,以下代码兼容IE和Chrome,Firefox ...

随机推荐

  1. 我的Android进阶之旅------>Android横竖屏切换总结

    在默认情况下当屏幕从竖评变到横屏时会触发 onConfigurationChanged 事件 在默认情况下会重新加载画面并显示和横屏一样的画面,这样会有2个问题,   * 布局问题,在竖屏 显示的布局 ...

  2. 从SignalTap II中获取“最真实”的仿真测试向量(ZZ)

         在实际工作中,经常会遇到这样的情况:在硬件调试中采用SignalTap II反复多次编译并最终捕获到问题的原因时,才会发现,原来这个问题是逻辑问题,是可以在仿真环境下发现并快速解决的.先前没 ...

  3. 使用C# .NET 将结构数组绑定到 Windows 窗体的方法

      本任务的内容 概要 要求 设计结构 向数组添加结构实例 将结构成员绑定到窗体控件 提供浏览数组的方式 分步示例 参考 概要 本文介绍如何向 Windows 窗体绑定结构数组. 该示例由一个 Win ...

  4. SDWebImage浅析

    第一部分 SDWebImage库的作用: 通过对UIImageView的类别扩展来实现异步加载替换图片的工作. 主要用到的对象: 1)UIImageView(WebCache)类别,入口封装,实现读取 ...

  5. windows安装pywin32

    下载旧版 https://sourceforge.net/projects/pywin32/files/pywin32/ 下载新版 https://github.com/mhammond/pywin3 ...

  6. JavaWeb:实现文件上传与下载

    JavaWeb:实现文件上传与下载 文件上传前端处理 本模块使用到的前端Ajax库为Axio,其地址为GitHub官网. 关于文件上传 上传文件就是把客户端的文件发送给服务器端. 在常见情况(不包含文 ...

  7. Vuex的入门教程

    前言 在 Vue.js 的项目中,如果项目结构简单, 父子组件之间的数据传递可以使用  props 或者 $emit 等方式,详细点击这篇文章查看. 但是如果是大型项目,很多时候都需要在子组件之间传递 ...

  8. 移植opencv2.4.9到android过程记录

    http://blog.csdn.net/brightming/article/details/50606463 在移植到arm开发板的时候已经说过,OpenCV已经为各平台准备了一套cmake交叉编 ...

  9. @MarkFan 口语练习录音 20140423 [风雨哈佛路.Homeless To Harvard口语录音]

    世界在转动,你只是一粒尘埃 没有你,世界照样在转 现实不会按照你的意识去改变的 一些人的需求 一些人的意志要比你更强 严酷的生活会让人不知所措 所以他们久久地困在挫败中 我们生气地抱怨,而对整体的形势 ...

  10. 【iOS和HTML 5交互】iOS中加载html5调用html方法和修改html5内容

    近期项目开发中用到了这方面的技术了,那我们一起来看看. 1.利用webView控件加载本地html5或者网络上html5 2.设置控制器为webView的代理,遵守协议 3.实现代理方法webView ...