获取文本区域中插入符号的偏移位置(以像素为单位)[重复]
Posted
技术标签:
【中文标题】获取文本区域中插入符号的偏移位置(以像素为单位)[重复]【英文标题】:Get the offset position of the caret in a textarea in pixels [duplicate] 【发布时间】:2013-04-19 05:40:50 【问题描述】:在我的项目中,我试图在textarea
中获取插入符号的偏移位置,以像素为单位。这可以做到吗?
在这里问之前,我已经经历了很多links,尤其是Tim Down 的,但我找不到适用于IE8+、Chrome 和Firefox 的解决方案。好像是Tim Down is working on this。
我发现的一些other links 有很多问题,比如找不到插入符号位置的顶部偏移量。
我正在尝试获取插入符号的偏移位置,因为我想通过根据插入符号的偏移位置定位它来在 textarea
内显示一个自动完成建议框。
PS:我不能使用contenteditable div
,因为我写了很多与textarea
相关的代码。
【问题讨论】:
这个你试过了吗:***.com/a/1909997/2107024作者说IE可以用。 @MythThrazz 如果我是对的,那将返回 textarea 中插入符号的索引位置,而不是插入符号的偏移位置。 “偏移位置”和“索引位置”有什么区别? @AaronDigulla 使用索引位置,我只能得到 left 值(大约基于字母的宽度),但不能得到 top 值。 @DuplicateVoters,这个问题之前确实有人问过,目前它的答案可能很好地满足了 OP 的需求(我还没有测试过),但我想指出指出在提出和回答这个问题时,“之前已经提出并已经有答案的问题”还没有解决 OP 需要在像素中获取文本区域中插入符号的偏移位置的答案. 【参考方案1】:您可以创建一个单独的(不可见的)元素,并从开始到光标位置用 textarea 内容填充它。 Textarea 和“克隆”应该具有匹配的 CSS(字体属性、填充/边距/边框和宽度)。然后将这些元素堆叠在一起。
让我从一个工作示例开始,然后浏览代码:http://jsfiddle.net/g7rBk/
Updated Fiddle(修复了 IE8)
html:
<textarea id="input"></textarea>
<div id="output"><span></span></div>
<div id="xy"></div>
Textarea 是不言自明的。输出是一个隐藏元素,我们将向其传递文本内容并进行测量。重要的是我们将使用内联元素。 “xy” div 只是用于测试目的的指标。
CSS:
/* identical styling to match the dimensions and position of textarea and its "clone"
*/
#input, #output
position:absolute;
top:0;
left:0;
font:14px/1 monospace;
padding:5px;
border:1px solid #999;
white-space:pre;
margin:0;
background:transparent;
width:300px;
max-width:300px;
/* make sure the textarea isn't obscured by clone */
#input
z-index:2;
min-height:200px;
#output
border-color:transparent;
/* hide the span visually using opacity (not display:none), so it's still measurable; make it break long words inside like textarea does. */
#output span
opacity:0;
word-wrap: break-word;
overflow-wrap: break-word;
/* the cursor position indicator */
#xy
position:absolute;
width:4px;
height:4px;
background:#f00;
/* get references to DOM nodes we'll use */
var input = document.getElementById('input'),
output = document.getElementById('output').firstChild,
position = document.getElementById('position'),
/* And finally, here it goes: */
update = function()
/* Fill the clone with textarea content from start to the position of the caret. You may need to expand here to support older IE [1]. The replace /\n$/ is necessary to get position when cursor is at the beginning of empty new line.
*/
output.innerHTML = input.value.substr( 0, input.selectionStart ).replace(/\n$/,"\n\001");
/* the fun part!
We use an inline element, so getClientRects[2] will return a collection of rectangles wrapping each line of text.
We only need the position of the last rectangle.
*/
var rects = output.getClientRects(),
lastRect = rects[ rects.length - 1 ],
top = lastRect.top - input.scrollTop,
left = lastRect.left+lastRect.width;
/* position the little div and see if it matches caret position :) */
xy.style.cssText = "top: "+top+"px;left: "+left+"px";
[1]Caret position in textarea, in characters from the start
[2]https://developer.mozilla.org/en/docs/DOM/element.getClientRects
编辑:此示例仅适用于固定宽度的文本区域。要使其与用户可调整大小的 textarea 一起使用,您需要向 resize 事件添加一个事件侦听器,并设置 #output 尺寸以匹配新的 #input 尺寸。
【讨论】:
【参考方案2】:这是一种使用rangyinputs、rangy 和jQuery 的方法。
它基本上将整个文本从textarea
复制到相同大小的div
中。我设置了一些 CSS 以确保在每个浏览器中,textarea
和 div
以完全相同的方式包装它们的内容。
单击textarea
时,我读出插入符号位于哪个字符索引处,然后在div
内的同一索引处插入插入符号span
。仅通过这样做,如果用户在行首单击,我最终会遇到插入符号span
跳回上一行的问题。为了解决这个问题,我检查前一个字符是否为space
(这将允许发生换行),如果是true
,我将其换成span
,然后我换行下一个单词(直接在插入符号位置之后)在span
中。现在我比较这两个span
之间的最高值,如果它们不同,就会发生一些包装,所以我假设#nextword
span
的top
和left
值是等价的到插入符号位置。
这种方法仍然可以改进,我确信我没有想到所有可能出错的地方,即使我想到了,我也没有像我没有那样为所有这些问题实施修复目前没有时间这样做,您需要查看一些内容:
目前,它在 Windows 8 上使用最新版本的 Chrome、Firefox、IE 和 Safari 浏览器的工作方式完全相同。不过我的测试并不是很严格。
这是一个jsFiddle。
我希望它会对你有所帮助,至少它可能会给你一些想法。
一些特点:
我为您添加了一个位于正确位置的ul
,并修复了一个 Firefox 问题,即在 DOM 操作后,textarea
选择未重新设置回其原始位置。
我已添加 IE7 - IE9 支持并修复了 cmets 中指出的多词选择问题。
我添加了对使用 Enter 和连续多个空格插入的硬回车的支持。
我已修复 ctrl+shift+左箭头 文本选择方法的默认行为问题。
JavaScript
function getTextAreaXandY()
// Don't do anything if key pressed is left arrow
if (e.which == 37) return;
// Save selection start
var selection = $(this).getSelection();
var index = selection.start;
// Copy text to div
$(this).blur();
$("div").text($(this).val());
// Get current character
$(this).setSelection(index, index + 1);
currentcharacter = $(this).getSelection().text;
// Get previous character
$(this).setSelection(index - 1, index)
previouscharacter = $(this).getSelection().text;
var start, endchar;
var end = 0;
var range = rangy.createRange();
// If current or previous character is a space or a line break, find the next word and wrap it in a span
var linebreak = previouscharacter.match(/(\r\n|\n|\r)/gm) == undefined ? false : true;
if (previouscharacter == ' ' || currentcharacter == ' ' || linebreak)
i = index + 1; // Start at the end of the current space
while (endchar != ' ' && end < $(this).val().length)
i++;
$(this).setSelection(i, i + 1)
var sel = $(this).getSelection();
endchar = sel.text;
end = sel.start;
range.setStart($("div")[0].childNodes[0], index);
range.setEnd($("div")[0].childNodes[0], end);
var nextword = range.toHtml();
range.deleteContents();
var position = $("<span id='nextword'>" + nextword + "</span>")[0];
range.insertNode(position);
var nextwordtop = $("#nextword").position().top;
// Insert `#caret` at the position of the caret
range.setStart($("div")[0].childNodes[0], index);
var caret = $("<span id='caret'></span>")[0];
range.insertNode(caret);
var carettop = $("#caret").position().top;
// If preceding character is a space, wrap it in a span
if (previouscharacter == ' ')
range.setStart($("div")[0].childNodes[0], index - 1);
range.setEnd($("div")[0].childNodes[0], index);
var prevchar = $("<span id='prevchar'></span>")[0];
range.insertNode(prevchar);
var prevchartop = $("#prevchar").position().top;
// Set textarea selection back to selection start
$(this).focus();
$(this).setSelection(index, selection.end);
// If the top value of the previous character span is not equal to the top value of the next word,
// there must have been some wrapping going on, the previous character was a space, so the wrapping
// would have occured after this space, its safe to assume that the left and top value of `#nextword`
// indicate the caret position
if (prevchartop != undefined && prevchartop != nextwordtop)
$("label").text('X: ' + $("#nextword").position().left + 'px, Y: ' + $("#nextword").position().top);
$('ul').css('left', ($("#nextword").position().left) + 'px');
$('ul').css('top', ($("#nextword").position().top + 13) + 'px');
// if not, then there was no wrapping, we can take the left and the top value from `#caret`
else
$("label").text('X: ' + $("#caret").position().left + 'px, Y: ' + $("#caret").position().top);
$('ul').css('left', ($("#caret").position().left) + 'px');
$('ul').css('top', ($("#caret").position().top + 14) + 'px');
$('ul').css('display', 'block');
$("textarea").click(getTextAreaXandY);
$("textarea").keyup(getTextAreaXandY);
HTML
<div></div>
<textarea>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.</textarea>
<label></label>
<ul>
<li>Why don't you type this..</li>
</ul>
CSS
body
font-family: Verdana;
font-size: 12px;
line-height: 14px;
textarea, div
font-family: Verdana;
font-size: 12px;
line-height: 14px;
width: 300px;
display: block;
overflow: hidden;
border: 1px solid black;
padding: 0;
margin: 0;
resize: none;
min-height: 300px;
position: absolute;
-moz-box-sizing: border-box;
white-space: pre-wrap;
span
display: inline-block;
height: 14px;
position: relative;
span#caret
display: inline;
label
display: block;
margin-left: 320px;
ul
padding: 0px;
margin: 9px;
position: absolute;
z-index: 999;
border: 1px solid #000;
background-color: #FFF;
list-style-type:none;
display: none;
@media screen and (-webkit-min-device-pixel-ratio:0)
span
white-space: pre-wrap;
div
/* Firefox wrapping fix */
-moz-padding-end: 1.5px;
-moz-padding-start: 1.5px;
/* IE8/IE9 wrapping fix */
padding-right: 5px\0/;
width: 295px\0/;
span#caret
display: inline-block\0/;
【讨论】:
【参考方案3】:与其他答案中提供的相比,以像素为单位获取插入符号位置的解决方案要简单得多。
请注意,这个问题与 2008 年的问题重复,我已经回答了 here。我只会在那个链接上保留答案,因为这个问题应该在几年前就已经被关闭了。
答案副本
我为meteor-autocomplete 寻找了一个 textarea caret 坐标插件,因此我评估了 GitHub 上的所有 8 个插件。到目前为止,获胜者是来自 Component 的textarea-caret-position。
特点
像素精度 没有任何依赖关系 浏览器兼容性:Chrome、Safari、Firefox(尽管有twobugs)、IE9+;可以工作,但未在 Opera、IE8 或更早版本中测试 支持任何字体系列和大小,以及文本转换 文本区域可以有任意填充或边框 不会被文本区域中的水平或垂直滚动条弄糊涂 支持硬回车、制表符(IE 除外)和文本中的连续空格 比文本区域中的列长的行的正确位置 在换行长字时,在行尾没有"ghost" position in the empty space这是一个演示 - http://jsfiddle.net/dandv/aFPA7/
工作原理
镜子<div>
是在屏幕外创建的,其样式与<textarea>
完全相同。然后,直到插入符号的文本区域的文本被复制到 div 中,并在其后插入一个<span>
。然后,将 span 的文本内容设置为 textarea 中文本的其余部分,以忠实再现虚假 div 中的换行。
这是唯一能保证处理与换行长线有关的所有边缘情况的方法。 GitHub 也使用它来确定其 @ 用户下拉列表的位置。
【讨论】:
非常详细的答案【参考方案4】:工作示例的 JsFiddle:http://jsfiddle.net/42zHC/2/
基本上,我们计算出有多少列适合宽度(因为它将是等宽的)。我们必须强制滚动条始终存在,否则计算将关闭。然后我们除以适合宽度的列数,得到每个字符的 x 偏移量。然后我们在 textarea 上设置行高。由于我们知道一行中有多少个字符,我们可以将其除以字符数并得到行号。有了线高,我们现在有了 y 偏移量。然后我们得到 textarea 的 scrollTop 并减去它,这样一旦它开始使用滚动条,它仍然会显示在正确的位置。
Javascript:
$(document).ready(function ()
var cols = document.getElementById('t').cols;
var width = document.getElementById('t').clientWidth;
var height = $('textarea').css('line-height');
var pos = $('textarea').position();
$('#t').on('keyup', function ()
el = document.getElementById("t");
if (el.selectionStart)
selection = el.selectionStart;
else if (document.selection)
el.focus();
var r = document.selection.createRange();
if (r == null)
selection = 0;
var re = el.createTextRange(),
rc = re.duplicate();
re.moveToBookmark(r.getBookmark());
rc.setEndPoint('EndToStart', re);
selection = rc.text.length;
else selection = 0
var row = Math.floor((selection-1) / cols);
var col = (selection - (row * cols));
var x = Math.floor((col*(width/cols)));
var y = (parseInt(height)*row);
$('span').html("row: " + row + "<br>columns" + col + "<br>width: " + width + "<br>x: " + x +"px<br>y: " + y +"px<br>Scrolltop: "+$(this).scrollTop()).css('top',pos.top+y-$(this).scrollTop()).css('left',pos.left+x+10);
);
);
HTML:
<textarea id="t"></textarea>
<br>
<span id="tooltip" style="background:yellow"></span>
CSS:
textarea
height: 80px;
line-height: 12px;
overflow-y:scroll;
span
position: absolute;
【讨论】:
感谢您的回复。它很酷,但有一些限制,比如当我按下回车键并输入内容时它不起作用。 :) 取决于字体是等宽字体是一个相当大的、不必要的限制。 @Mr_Green,我查看了 GitHub 上的所有 textarea caret position 插件,并改进了最强大、最强大和最简单的一个。它没有依赖项,适用于 Firefox、Chrome 和 IE9(可能也适用于旧版浏览器),并且只有 80 行代码。 @DanDascalescu 谢谢.. 不知何故,我没有收到您的 ping 通知。该项目已完成,所以我没有对其进行测试。我希望它对其他人有帮助:)。顺便说一句,您可以通过添加show
来使用 jsfiddle 检查旧版浏览器。类似jsfiddle.net/abcd/show
..【参考方案5】:
我无法得到类似的工作,所以我的解决方案是在 textarea 中找到插入符号的字符位置,剪切当前段落并将其显示在 textarea 旁边。
使用偏移量,我在可编辑文本的这个视图中放置了一个假光标(div
、display:inline
、1px 宽、border-left: 1px solid black
)。
这样,您可以创建一个视觉反馈区域,您可以在其中显示效果的结果(很像 *** 在您编写答案时所做的那样)。
【讨论】:
对不起,我听不懂你的解释。 这是一种解决方法。我没有尝试找到光标位置,而是向用户展示了文本的外观。但这是否有用取决于您为什么需要知道插入符号的位置。 因为我试图展示一个由 ul 元素组成的建议框并根据这些偏移位置值定位它。以上是关于获取文本区域中插入符号的偏移位置(以像素为单位)[重复]的主要内容,如果未能解决你的问题,请参考以下文章