获取输入元素的像素的光标或文本位置

IE允许我在输入元素中创建一个文本范围,我可以在其上调用getBoundingClientRect()并获取某个字符或光标/插入符的像素位置。 有没有办法在其他浏览器中以像素为单位获取某个角色的位置?

 var input = $("#myInput")[0]; var pixelPosition = null; if (input.createTextRange) { var range = input.createTextRange(); range.moveStart("character", 6); pixelPosition = range.getBoundingClientRect(); } else { // Is there any way to create a range on an input's value? } 

我正在使用jQuery,但我怀疑它能否解决我的问题。 我期待一个纯JavaScript解决方案,如果有的话,但欢迎jQuery的答案。

演示
我编写了一个行为符合预期的函数。 这里有一个非常详细的演示面板: 小提琴: http : //jsfiddle.net/56Rep/5/
演示中的界面不言自明。

问题中要求的function将在我的function中实现如下:
var pixelPosition = getTextBoundingRect(input, 6)

function依赖
更新 :该函数是纯JavaScript,不依赖于任何插件或框架!
该函数假定存在getBoundingClientRect方法。 文本范围在支持时使用。 否则,使用我的函数逻辑实现function。

function逻辑
代码本身包含几条注释。 这部分更详细。

  1. 创建一个临时

    容器。

  2. 创建1 – 3个元素。 每个跨度包含输入值的一部分(偏移0到selectionStartselectionStartselectionEndselectionEnd到string的结尾,只有第二个span是meaninngful)。
  3. input元素中的几个重要样式属性将复制到这些

    标记。 仅复制重要的样式属性。 例如,不复制color ,因为它不会以任何方式影响角色的偏移。 #1

  4. 位于文本节点的确切位置(输入值)。 考虑边框和填充,以确保临时

    正确定位。

  5. 创建一个变量,它保存div.getBoundingClientRect()的返回值。
  6. 除非参数debug设置为true, 否则将删除临时

  7. 该函数返回ClientRect对象。 有关此对象的更多信息,请参阅此页面 。 该演示还显示了一个属性列表: topleftrightbottomheightwidth

#1getBoundingClientRect() (以及一些次要属性)用于确定输入元素的位置。 然后,添加填充和边框宽度,以获得文本节点的实际位置。

已知的问题
getComputedStylefont-family返回错误的值时,遇到了唯一的不一致情况:当页面未定义font-family属性时,computedStyle返回一个不正确的值(即使Firebug遇到此问题;环境:Linux, Firefox 3.6.23,字体“Sans Serif”)。

如在演示中可见,定位有时略微偏离(几乎为零,始终小于1像素)。

技术限制可防止脚本在移动内容时获取文本片段的确切偏移量,例如,当输入字段中的第一个可见字符不等于第一个值的字符时。

 // @author Rob W http://stackoverflow.com/users/938089/rob-w // @name getTextBoundingRect // @param input Required HTMLElement with `value` attribute // @param selectionStart Optional number: Start offset. Default 0 // @param selectionEnd Optional number: End offset. Default selectionStart // @param debug Optional boolean. If true, the created test layer // will not be removed. function getTextBoundingRect(input, selectionStart, selectionEnd, debug) { // Basic parameter validation if(!input || !('value' in input)) return input; if(typeof selectionStart == "string") selectionStart = parseFloat(selectionStart); if(typeof selectionStart != "number" || isNaN(selectionStart)) { selectionStart = 0; } if(selectionStart < 0) selectionStart = 0; else selectionStart = Math.min(input.value.length, selectionStart); if(typeof selectionEnd == "string") selectionEnd = parseFloat(selectionEnd); if(typeof selectionEnd != "number" || isNaN(selectionEnd) || selectionEnd < selectionStart) { selectionEnd = selectionStart; } if (selectionEnd < 0) selectionEnd = 0; else selectionEnd = Math.min(input.value.length, selectionEnd); // If available (thus IE), use the createTextRange method if (typeof input.createTextRange == "function") { var range = input.createTextRange(); range.collapse(true); range.moveStart('character', selectionStart); range.moveEnd('character', selectionEnd - selectionStart); return range.getBoundingClientRect(); } // createTextRange is not supported, create a fake text range var offset = getInputOffset(), topPos = offset.top, leftPos = offset.left, width = getInputCSS('width', true), height = getInputCSS('height', true); // Styles to simulate a node in an input field var cssDefaultStyles = "white-space:pre;padding:0;margin:0;", listOfModifiers = ['direction', 'font-family', 'font-size', 'font-size-adjust', 'font-variant', 'font-weight', 'font-style', 'letter-spacing', 'line-height', 'text-align', 'text-indent', 'text-transform', 'word-wrap', 'word-spacing']; topPos += getInputCSS('padding-top', true); topPos += getInputCSS('border-top-width', true); leftPos += getInputCSS('padding-left', true); leftPos += getInputCSS('border-left-width', true); leftPos += 1; //Seems to be necessary for (var i=0; i 0) appendPart(0, selectionStart); var fakeRange = appendPart(selectionStart, selectionEnd); if(textLen > selectionEnd) appendPart(selectionEnd, textLen); // Styles to inherit the font styles of the element fakeClone.style.cssText = cssDefaultStyles; // Styles to position the text node at the desired position fakeClone.style.position = "absolute"; fakeClone.style.top = topPos + "px"; fakeClone.style.left = leftPos + "px"; fakeClone.style.width = width + "px"; fakeClone.style.height = height + "px"; document.body.appendChild(fakeClone); var returnValue = fakeRange.getBoundingClientRect(); //Get rect if (!debug) fakeClone.parentNode.removeChild(fakeClone); //Remove temp return returnValue; // Local functions for readability of the previous code function appendPart(start, end){ var span = document.createElement("span"); span.style.cssText = cssDefaultStyles; //Force styles to prevent unexpected results span.textContent = text.substring(start, end); fakeClone.appendChild(span); return span; } // Computing offset position function getInputOffset(){ var body = document.body, win = document.defaultView, docElem = document.documentElement, box = document.createElement('div'); box.style.paddingLeft = box.style.width = "1px"; body.appendChild(box); var isBoxModel = box.offsetWidth == 2; body.removeChild(box); box = input.getBoundingClientRect(); var clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0, scrollTop = win.pageYOffset || isBoxModel && docElem.scrollTop || body.scrollTop, scrollLeft = win.pageXOffset || isBoxModel && docElem.scrollLeft || body.scrollLeft; return { top : box.top + scrollTop - clientTop, left: box.left + scrollLeft - clientLeft}; } function getInputCSS(prop, isnumber){ var val = document.defaultView.getComputedStyle(input, null).getPropertyValue(prop); return isnumber ? parseFloat(val) : val; } } 

2014年5月更新:令人难以置信的轻量级和强大的textarea-caret-position 组件库现在也支持 ,所有其他答案都已过时。

有关演示,请访问http://jsfiddle.net/dandv/aFPA7/

感谢Rob W对RTL支持的启发。

我最终创建了一个隐藏的模拟输入,从一个绝对定位并与输入类似的样式。 我将该范围的文本设置为输入的值,直到我想要找到的位置的字符。 我在输入之前插入跨度并得到它的偏移量:

 function getInputTextPosition(input, charOffset) { var pixelPosition = null; if (input.createTextRange) { var range = input.createTextRange(); range.moveStart("character", charOffset); pixelPosition = range.getBoundingClientRect(); } else { var text = input.value.substr(0, charOffset).replace(/ $/, "\xa0"); var sizer = $("#sizer").insertBefore(input).text(text); pixelPosition = sizer.offset(); pixelPosition.left += sizer.width(); if (!text) sizer.text("."); // for computing height. An empty span returns 0 pixelPosition.bottom = pixelPosition.top + sizer.height(); } return pixelPosition } 

我的sizer跨度的css:

 #sizer { position: absolute; display: inline-block; visibility: hidden; margin: 3px; /* simulate padding and border without affecting height and width */ font-family: "segoe ui", Verdana, Arial, Sans-Serif; font-size: 12px; } 

2016更新:更现代的基于HTML5的解决方案是使用contenteditable属性。

 
Block of regular text, and text of interest

我们现在可以使用jquery offset()找到span的位置。 当然, 标签可以预先插入或动态插入。