javascript 获取输入元素的光标或文本位置(以像素为单位)
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/6930578/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Get cursor or text position in pixels for input element
提问by gilly3
IE allows me to create a text range in an input element, upon which I can call getBoundingClientRect()
and get the position in pixels of a certain character or the cursor/caret. Is there any way of getting the position of a certain character in pixelsin other browsers?
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?
}
I'm using jQuery, but I doubt it will be able to address my situation. I expect a pure JavaScript solution, if any, but jQuery answers are welcome.
我正在使用 jQuery,但我怀疑它是否能够解决我的情况。我希望有一个纯 JavaScript 解决方案(如果有),但欢迎使用 jQuery 答案。
采纳答案by gilly3
I ended up creating a hidden mock input out of a span positioned absolutely and styled similarly to the input. I set the text of that span to the value of the input up to the character whose position I want to find. I insert the span before the input and get it's offset:
我最终从绝对定位的跨度中创建了一个隐藏的模拟输入,其样式与输入类似。我将该跨度的文本设置为输入的值,直到我要查找其位置的字符。我在输入之前插入跨度并得到它的偏移量:
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
}
The css for my sizer span:
我的 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;
}
回答by Rob W
Demo
I have written a function which behaves as expected. A very detailed demonstration panel can be found here: Fiddle: http://jsfiddle.net/56Rep/5/
The interface in the demo is self-explanatory.
演示
我编写了一个按预期运行的函数。一个非常详细的演示面板可以在这里找到:Fiddle: http://jsfiddle.net/56Rep/5/
演示中的界面是不言自明的。
The functionality as requested in the question would be implemented in my function as follows:
var pixelPosition = getTextBoundingRect(input, 6)
问题中要求的功能将在我的函数中实现,如下所示:
var pixelPosition = getTextBoundingRect(input, 6)
Function dependencies
Updated: The function is pure JavaScript, and not dependent on any plugin or framework!
The function assumes that the getBoundingClientRect
method exist. Text ranges are used when they're supported. Otherwise, the functionality is achieved using my function logic.
函数依赖
更新:函数纯JavaScript,不依赖任何插件或框架!
该函数假定该getBoundingClientRect
方法存在。文本范围在受支持时使用。否则,功能是使用我的功能逻辑实现的。
Function logic
The code itself contains several comments. This part goes in a deeper detail.
功能逻辑
代码本身包含几个注释。这部分有更深入的细节。
- One temporary
<div>
container is created. - 1 - 3
<span>
elements are created. Each span holds a part of the input's value (offsets 0 toselectionStart
,selectionStart
toselectionEnd
,selectionEnd
to end of string, only the second span is meaninngful). - Several significant style properties from the input element are copied to these
<div>
and<span>
tags. Only significant style properties are copied. For example,color
is not copied, because it does not affect the offsets of a character in any way.#1 - The
<div>
is positioned at the exact position of the text node(input's value). Borders and paddings are taken into account, to make sure that the temporary<div>
is correctlypositioned. - A variable is created, which holds the return value of
div.getBoundingClientRect()
. - The temporary
<div>
is removed, unlessparameterdebug
is set to true. - The function returns the
ClientRect
object. For more information about this object, see this page. The demoalso shows a list of properties:top
,left
,right
,bottom
,height
andwidth
.
- 创建了一个临时
<div>
容器。 <span>
创建1 - 3 个元素。每个跨度包含输入值的一部分(偏移 0 到selectionStart
,selectionStart
到selectionEnd
,selectionEnd
到字符串结尾,只有第二个跨度是有意义的)。- 来自输入元素的几个重要的样式属性被复制到这些
<div>
和<span>
标签中。仅复制重要的样式属性。例如,color
不被复制,因为它不会以任何方式影响字符的偏移量。#1 - 在
<div>
被定位在的确切位置文本节点(输入的值)。边框和内边距考虑在内,以确保临时<div>
被正确定位。 - 创建了一个变量,它保存 的返回值
div.getBoundingClientRect()
。 - 临时
<div>
被删除,除非参数debug
设置为 true。 - 该函数返回
ClientRect
对象。有关此对象的更多信息,请参阅此页面。该演示还显示属性的列表:top
,left
,right
,bottom
,height
和width
。
#1: getBoundingClientRect()
(and some minor properties) is used to determine the position of the input element. Then, the padding and border width are added, to get the real position of a text node.
#1:(getBoundingClientRect()
和一些次要属性)用于确定输入元素的位置。然后,添加填充和边框宽度,以获得文本节点的实际位置。
Known issues
The only case of an inconsistency was encountered when getComputedStyle
returned a wrong value for font-family
: When a page hasn't defined a font-family
property, the computedStyle returns an incorrect value (even Firebug is experiencing this issue; environment: Linux, Firefox 3.6.23, font "Sans Serif").
已知问题返回错误值
时遇到的唯一不一致情况:当页面未定义属性时,计算样式返回错误值(即使 Firebug 也遇到此问题;环境:Linux、Firefox 3.6.23、字体“无衬线”)。getComputedStyle
font-family
font-family
As visible in the demo, the positioning is sometimes slightly off (almost zero, always smaller than 1 pixel).
如演示中所示,定位有时会略微偏离(几乎为零,始终小于 1 像素)。
Technical restrictions prevents the script from getting the exact offset of a text fragment when the contents has been moved, e.g. when the first visible character in an input field does not equal the first value's character.
当内容被移动时,例如当输入字段中的第一个可见字符不等于第一个值的字符时,技术限制会阻止脚本获取文本片段的确切偏移量。
Code
代码
// @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<listOfModifiers.length; i++) {
var property = listOfModifiers[i];
cssDefaultStyles += property + ':' + getInputCSS(property) +';';
}
// End of CSS variable checks
var text = input.value,
textLen = text.length,
fakeClone = document.createElement("div");
if(selectionStart > 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;
}
}
回答by Dan Dascalescu
May 2014 update:The incredibly lightweight and robust textarea-caret-positionComponentlibrary now supports <input type="text">
as well, rendering all other answers obsolete.
2014 年 5 月更新:令人难以置信的轻量级和强大的textarea-caret-position组件库现在也支持<input type="text">
,使所有其他答案都过时了。
A demo is available at http://jsfiddle.net/dandv/aFPA7/
演示可在http://jsfiddle.net/dandv/aFPA7/ 获得
Thanks to Rob W for inspiration towards RTL support.
感谢 Rob W 对 RTL 支持的启发。
回答by S B
2016 update:A more modern HTML5 based solution would be to use the contenteditable
property.
2016 年更新:更现代的基于 HTML5 的解决方案是使用该contenteditable
属性。
<div contenteditable="true"> <!-- behaves as input -->
Block of regular text, and <span id='interest'>text of interest</span>
</div>
We can now find the position of the span using jquery offset()
. And of course, the <span>
tags can be inserted upfront or dynamically.
我们现在可以使用 jquery 找到跨度的位置offset()
。当然,<span>
标签可以预先插入或动态插入。