以像素为单位获取文本区域内插入符号的偏移位置[duplicate]

时间:2022-06-25 17:25:54

This question already has an answer here:

这个问题已经有了答案:

In my project I'm trying to get the offset position of the caret in a textarea in pixels. Can this be done?

在我的项目中,我试着用像素的textarea来获得插入符号的偏移位置。这个可以做吗?

Before asking here, I have gone through many links, especially Tim Down's, but I couldn't find a solution which works in IE8+, Chrome and Firefox. It seems Tim Down is working on this.

在这里提问之前,我已经浏览了很多链接,特别是Tim Down的,但是我找不到一个可以在IE8+, Chrome和Firefox中运行的解决方案。看来蒂姆在研究这个。

Some other links which I have found have many issues like not finding the top offset of the caret position.

我发现的其他一些链接有很多问题,比如没有找到插入符号位置的顶部偏移量。

I am trying to get the offset position of the caret because I want to show an auto-complete suggestion box inside the textarea by positioning it based on the offset position of the caret.

我想要得到caret的偏移位置,因为我想要在textarea内显示一个自动完成的建议框,根据插入符号的偏移位置来定位它。

PS: I can't use a contenteditable div because I have written lots of code related to a textarea.

PS:我不能使用contenteditable div,因为我已经编写了大量与textarea相关的代码。

5 个解决方案

#1


15  

Here's an approach using rangyinputs, rangy and jQuery.

这里有一个使用rangyinput、rangy和jQuery的方法。

It basically copies the whole text from inside the textarea into a div of the same size. I have set some CSS to ensure that in every browser, the textarea and the div wrap their content in exactly the same way.

它基本上是将整个文本从textarea中复制到相同大小的div中。我已经设置了一些CSS,以确保在每个浏览器中,textarea和div都以完全相同的方式包装它们的内容。

When the textarea is clicked, I read out at which character index the caret is positioned, then I insert a caret span at the same index inside the div. By only doing that I ended up having an issue with the caret span jumping back to the previous line if the user clicked at the start of a line. To fix that I check if the previous character is a space (which would allow a wrap to occur), if that is true, I wrap it in a span, and I wrap the next word (the one directly after the caret position) in a span. Now I compare the top values between these two span's, if they differ, there was some wrapping going on, so I assume that the top and the left value of the #nextword span are equivalent to the caret position.

当单击文本区域,我读出的字符索引插入符号的位置,然后插入一个脱字符号跨度在同一指数在div。只有这样做我最后有一个问题与脱字符号跨跳回前一行如果用户点击在一行的开始。为了解决这个问题,我检查前一个字符是否为空格(这将允许换行),如果这是真的,我将它换行,并将下一个单词(插入符号位置后面的那个)换行。现在我比较这两个span之间的上值,如果它们不同,就会有一些换行,所以我假设#nextword span的上值和左值等价于插入符号位置。

This approach can still be improved upon, I'm sure I haven't thought of everything that could possibly go wrong, and even if I have, then I haven't bothered implementing a fix for all of them as I don't have the time to do so at the moment, a number of things that you would need to look at:

这种方法仍然可以改进,我相信我还没有想到一切可能出错,甚至如果我有,我就懒得实施修复所有的我没有时间这样做,很多事情你需要看看:

  • it doesn't yet handle hard returns inserted with Enter (fixed)
  • 它还没有处理使用Enter(固定)插入的硬返回
  • positioning breaks when entering multiple spaces in a row (fixed)
  • 当进入一行中的多个空间时,定位中断(固定)
  • I think hyphens would allow a content wrap to occur as well..
  • 我认为连字符也会允许内容包装。

Currently it works exactly the same way across browsers here on Windows 8 with the latest versions of Chrome, Firefox, IE and Safari. My testing has not been very rigorous though.

目前,它在Windows 8上与最新版本的Chrome、Firefox、IE和Safari完全相同。我的测试不是很严格。

Here's a jsFiddle.

这是一个jsFiddle。

I hope it will help you, at the very least it might give you some ideas to build on.

我希望它能帮助你,至少它能给你一些想法。

Some Features:

  • I have included a ul for you which is positioned in the right spot, and fixed a Firefox issue where the textarea selection was not re-set back to its original spot after the DOM manipulations.

    我已经为您包含了一个位于正确位置的ul,并修复了一个Firefox问题,在DOM操作之后,textarea选项没有被重新设置回原来的位置。

  • I have added IE7 - IE9 support and fixed the multiple word selection issue pointed out in the comments.

    我增加了IE7 - IE9的支持,并修正了评论中指出的多字选择问题。

  • I have added support for hard returns inserted with Enter and multiple spaces in a row.

    我已经添加了对硬返回的支持,并在一行中插入了Enter和多个空格。

  • I have fixed an issue with the default behaviour for the ctrl+shift+left arrow text selection method.

    我修正了ctrl+shift+左箭头文本选择方法的默认行为的问题。

JavaScript

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

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

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/;
}

#2


17  

You can create a separate (invisible) element and fill it with textarea content from start to the cursor position. Textarea and the "clone" should have matching CSS (font properties, padding/margin/border and width). Then stack these elements on top of each other.

您可以创建一个单独的(不可见的)元素,并使用从开始到光标位置的textarea内容填充它。Textarea和“克隆”应该具有匹配的CSS(字体属性、填充、空白/边框和宽度)。然后将这些元素叠加在一起。

Let me start with a working example, then walk through the code: http://jsfiddle.net/g7rBk/

让我从一个工作示例开始,然后遍历代码:http://jsfiddle.net/g7rBk/。

Updated Fiddle (with IE8 fix)

HTML:

HTML:

<textarea id="input"></textarea>
<div id="output"><span></span></div>
<div id="xy"></div>

Textarea is self-explanatory. Output is a hidden element to which we'll pass text content and make measures. What's important is that we'll use an inline element. the "xy" div is just an indicator for testing purposes.

是自解释的文本区域。输出是一个隐藏元素,我们将向其传递文本内容并进行度量。重要的是我们将使用一个内联元素。“xy”div仅仅是用于测试的指示器。

CSS:

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;
}

JavaScript:

JavaScript:

/* 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

[1]插入符号在textarea中的位置,从开始的字符

[2] https://developer.mozilla.org/en/docs/DOM/element.getClientRects

[2]https://developer.mozilla.org/en/docs/DOM/element.getClientRects

Edit: This example only works for fixed-width textarea. To make it work with user-resizable textarea you'd need to add an event listener to the resize event and set the #output dimensions to match new #input dimensions.

编辑:这个例子只适用于固定宽度的文本区域。要使它与用户可调整大小的textarea一起工作,您需要向调整大小事件添加一个事件监听器,并设置#output维度以匹配新的#输入维度。

#3


6  

There's a much simpler solution for getting the caret position in pixels, than what's been presented in the other answers.

有一种更简单的解决方案可以获得以像素为单位的插入符号位置,比其他答案中给出的要简单得多。

Note that this question is a duplicate of a 2008 one, and I've answered it here. I'll only maintain the answer at that link, since this question should have been closed as duplicate years ago.

注意,这个问题是2008年问题的翻版,我在这里已经回答过了。我只会在那个链接上保留答案,因为这个问题应该是重复的。

Copy of the answer

I've looked for a textarea caret coordinates plugin for meteor-autocomplete, so I've evaluated all the 8 plugins on GitHub. The winner is, by far, textarea-caret-position from Component.

我已经为meteor-autocomplete查找了一个textarea caret坐标插件,因此我对GitHub上的所有8个插件进行了评估。到目前为止,胜出者是来自组件的textar -caret-position。

Features

  • pixel precision
  • 像素精度
  • no dependencies whatsoever
  • 没有任何附件
  • browser compatibility: Chrome, Safari, Firefox (despite two bugs it has), IE9+; may work but not tested in Opera, IE8 or older
  • 浏览器兼容性:Chrome, Safari, Firefox(尽管有两个bug), IE9+;是否可以在Opera, IE8或更老的版本中测试
  • supports any font family and size, as well as text-transforms
  • 支持任何字体族和大小,以及文本转换
  • the text area can have arbitrary padding or borders
  • 文本区域可以有任意的填充或边框。
  • not confused by horizontal or vertical scrollbars in the textarea
  • 不要被文本区域中的水平或垂直滚动条迷惑
  • supports hard returns, tabs (except on IE) and consecutive spaces in the text
  • 支持硬返回、制表符(IE除外)和文本中的连续空格
  • correct position on lines longer than the columns in the text area
  • 在比文本区域中的列长一些的行上正确的位置
  • no "ghost" position in the empty space at the end of a line when wrapping long words
  • 没有“鬼”的位置,在空的空间的最后一行,当包装长字

Here's a demo - http://jsfiddle.net/dandv/aFPA7/

以像素为单位获取文本区域内插入符号的偏移位置[duplicate]

How it works

A mirror <div> is created off-screen and styled exactly like the <textarea>. Then, the text of the textarea up to the caret is copied into the div and a <span> is inserted right after it. Then, the text content of the span is set to the remainder of the text in the textarea, in order to faithfully reproduce the wrapping in the faux div.

镜像

在屏幕外创建,样式与 。然后,将span的文本内容设置为textarea中文本的其余部分,以便在人造div中忠实地重现包装。

This is the only method guaranteed to handle all the edge cases pertaining to wrapping long lines. It's also used by GitHub to determine the position of its @ user dropdown.

这是唯一可以保证处理与包装长线有关的所有边缘情况的方法。GitHub也用它来确定@用户下拉的位置。

#4


2  

JsFiddle of working example: http://jsfiddle.net/42zHC/2/

工作示例的JsFiddle: http://jsfiddle.net/42zHC/2/

Basically, we figure out how many columns fit in the width (since it will be monospace). We have to force scrollbars to always be there otherwise the calculation is off. Then we divide the number of columns that fit with the width, and we get the x offset per character. Then we set the line height on the textarea. Since we know how many characters are in a row, we can divide that with the number of characters and we get the row number. With the line height, we now have the y offset. Then we get the scrollTop of the textarea and subtract that, so that once it starts using the scrollbar, it still shows up in the right position.

基本上,我们算出宽度中有多少列(因为它是单空间的)。我们必须使滚动条始终保持在那里,否则计算就失败了,然后我们除以符合宽度的列数,得到每个字符的x偏移量。然后我们在textarea上设置行高度。因为我们知道一行中有多少个字符,我们可以用它除以字符数,得到行号。有了直线高度,我们现在有了y偏移量。然后我们得到textarea的滚动条并减去它,这样一旦它开始使用滚动条,它仍然会显示在正确的位置。

Javascript:

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:

HTML:

<textarea id="t"></textarea>
<br>
<span id="tooltip" style="background:yellow"></span>

CSS:

CSS:

textarea {
  height: 80px;
  line-height: 12px;
  overflow-y:scroll;
}
span {
  position: absolute;
}

#5


-1  

I couldn't get something similar to work, so my solution was to locate the character position of the caret in the textarea, cut out the current paragraph and display this next to the textarea.

我无法得到类似的东西,所以我的解决方案是在textarea中找到插入符号的字符位置,删除当前段落,并将其显示在textarea旁边。

Using the offset, I placed a fake cursor (div, display:inline, 1px wide, border-left: 1px solid black) in this view of the editable text.

使用偏移量,我在可编辑文本的视图中放置了一个伪游标(div, display:inline, 1px width, border-left: 1px solid black)。

This way, you can create a visual feedback area where you can show the result of effects (quite like * does when you write an answer).

通过这种方式,您可以创建一个视觉反馈区域,在那里您可以显示效果的结果(就像您编写答案时*所做的那样)。

#1


15  

Here's an approach using rangyinputs, rangy and jQuery.

这里有一个使用rangyinput、rangy和jQuery的方法。

It basically copies the whole text from inside the textarea into a div of the same size. I have set some CSS to ensure that in every browser, the textarea and the div wrap their content in exactly the same way.

它基本上是将整个文本从textarea中复制到相同大小的div中。我已经设置了一些CSS,以确保在每个浏览器中,textarea和div都以完全相同的方式包装它们的内容。

When the textarea is clicked, I read out at which character index the caret is positioned, then I insert a caret span at the same index inside the div. By only doing that I ended up having an issue with the caret span jumping back to the previous line if the user clicked at the start of a line. To fix that I check if the previous character is a space (which would allow a wrap to occur), if that is true, I wrap it in a span, and I wrap the next word (the one directly after the caret position) in a span. Now I compare the top values between these two span's, if they differ, there was some wrapping going on, so I assume that the top and the left value of the #nextword span are equivalent to the caret position.

当单击文本区域,我读出的字符索引插入符号的位置,然后插入一个脱字符号跨度在同一指数在div。只有这样做我最后有一个问题与脱字符号跨跳回前一行如果用户点击在一行的开始。为了解决这个问题,我检查前一个字符是否为空格(这将允许换行),如果这是真的,我将它换行,并将下一个单词(插入符号位置后面的那个)换行。现在我比较这两个span之间的上值,如果它们不同,就会有一些换行,所以我假设#nextword span的上值和左值等价于插入符号位置。

This approach can still be improved upon, I'm sure I haven't thought of everything that could possibly go wrong, and even if I have, then I haven't bothered implementing a fix for all of them as I don't have the time to do so at the moment, a number of things that you would need to look at:

这种方法仍然可以改进,我相信我还没有想到一切可能出错,甚至如果我有,我就懒得实施修复所有的我没有时间这样做,很多事情你需要看看:

  • it doesn't yet handle hard returns inserted with Enter (fixed)
  • 它还没有处理使用Enter(固定)插入的硬返回
  • positioning breaks when entering multiple spaces in a row (fixed)
  • 当进入一行中的多个空间时,定位中断(固定)
  • I think hyphens would allow a content wrap to occur as well..
  • 我认为连字符也会允许内容包装。

Currently it works exactly the same way across browsers here on Windows 8 with the latest versions of Chrome, Firefox, IE and Safari. My testing has not been very rigorous though.

目前,它在Windows 8上与最新版本的Chrome、Firefox、IE和Safari完全相同。我的测试不是很严格。

Here's a jsFiddle.

这是一个jsFiddle。

I hope it will help you, at the very least it might give you some ideas to build on.

我希望它能帮助你,至少它能给你一些想法。

Some Features:

  • I have included a ul for you which is positioned in the right spot, and fixed a Firefox issue where the textarea selection was not re-set back to its original spot after the DOM manipulations.

    我已经为您包含了一个位于正确位置的ul,并修复了一个Firefox问题,在DOM操作之后,textarea选项没有被重新设置回原来的位置。

  • I have added IE7 - IE9 support and fixed the multiple word selection issue pointed out in the comments.

    我增加了IE7 - IE9的支持,并修正了评论中指出的多字选择问题。

  • I have added support for hard returns inserted with Enter and multiple spaces in a row.

    我已经添加了对硬返回的支持,并在一行中插入了Enter和多个空格。

  • I have fixed an issue with the default behaviour for the ctrl+shift+left arrow text selection method.

    我修正了ctrl+shift+左箭头文本选择方法的默认行为的问题。

JavaScript

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

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

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/;
}

#2


17  

You can create a separate (invisible) element and fill it with textarea content from start to the cursor position. Textarea and the "clone" should have matching CSS (font properties, padding/margin/border and width). Then stack these elements on top of each other.

您可以创建一个单独的(不可见的)元素,并使用从开始到光标位置的textarea内容填充它。Textarea和“克隆”应该具有匹配的CSS(字体属性、填充、空白/边框和宽度)。然后将这些元素叠加在一起。

Let me start with a working example, then walk through the code: http://jsfiddle.net/g7rBk/

让我从一个工作示例开始,然后遍历代码:http://jsfiddle.net/g7rBk/。

Updated Fiddle (with IE8 fix)

HTML:

HTML:

<textarea id="input"></textarea>
<div id="output"><span></span></div>
<div id="xy"></div>

Textarea is self-explanatory. Output is a hidden element to which we'll pass text content and make measures. What's important is that we'll use an inline element. the "xy" div is just an indicator for testing purposes.

是自解释的文本区域。输出是一个隐藏元素,我们将向其传递文本内容并进行度量。重要的是我们将使用一个内联元素。“xy”div仅仅是用于测试的指示器。

CSS:

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;
}

JavaScript:

JavaScript:

/* 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

[1]插入符号在textarea中的位置,从开始的字符

[2] https://developer.mozilla.org/en/docs/DOM/element.getClientRects

[2]https://developer.mozilla.org/en/docs/DOM/element.getClientRects

Edit: This example only works for fixed-width textarea. To make it work with user-resizable textarea you'd need to add an event listener to the resize event and set the #output dimensions to match new #input dimensions.

编辑:这个例子只适用于固定宽度的文本区域。要使它与用户可调整大小的textarea一起工作,您需要向调整大小事件添加一个事件监听器,并设置#output维度以匹配新的#输入维度。

#3


6  

There's a much simpler solution for getting the caret position in pixels, than what's been presented in the other answers.

有一种更简单的解决方案可以获得以像素为单位的插入符号位置,比其他答案中给出的要简单得多。

Note that this question is a duplicate of a 2008 one, and I've answered it here. I'll only maintain the answer at that link, since this question should have been closed as duplicate years ago.

注意,这个问题是2008年问题的翻版,我在这里已经回答过了。我只会在那个链接上保留答案,因为这个问题应该是重复的。

Copy of the answer

I've looked for a textarea caret coordinates plugin for meteor-autocomplete, so I've evaluated all the 8 plugins on GitHub. The winner is, by far, textarea-caret-position from Component.

我已经为meteor-autocomplete查找了一个textarea caret坐标插件,因此我对GitHub上的所有8个插件进行了评估。到目前为止,胜出者是来自组件的textar -caret-position。

Features

  • pixel precision
  • 像素精度
  • no dependencies whatsoever
  • 没有任何附件
  • browser compatibility: Chrome, Safari, Firefox (despite two bugs it has), IE9+; may work but not tested in Opera, IE8 or older
  • 浏览器兼容性:Chrome, Safari, Firefox(尽管有两个bug), IE9+;是否可以在Opera, IE8或更老的版本中测试
  • supports any font family and size, as well as text-transforms
  • 支持任何字体族和大小,以及文本转换
  • the text area can have arbitrary padding or borders
  • 文本区域可以有任意的填充或边框。
  • not confused by horizontal or vertical scrollbars in the textarea
  • 不要被文本区域中的水平或垂直滚动条迷惑
  • supports hard returns, tabs (except on IE) and consecutive spaces in the text
  • 支持硬返回、制表符(IE除外)和文本中的连续空格
  • correct position on lines longer than the columns in the text area
  • 在比文本区域中的列长一些的行上正确的位置
  • no "ghost" position in the empty space at the end of a line when wrapping long words
  • 没有“鬼”的位置,在空的空间的最后一行,当包装长字

Here's a demo - http://jsfiddle.net/dandv/aFPA7/

以像素为单位获取文本区域内插入符号的偏移位置[duplicate]

How it works

A mirror <div> is created off-screen and styled exactly like the <textarea>. Then, the text of the textarea up to the caret is copied into the div and a <span> is inserted right after it. Then, the text content of the span is set to the remainder of the text in the textarea, in order to faithfully reproduce the wrapping in the faux div.

镜像

在屏幕外创建,样式与 。然后,将span的文本内容设置为textarea中文本的其余部分,以便在人造div中忠实地重现包装。

This is the only method guaranteed to handle all the edge cases pertaining to wrapping long lines. It's also used by GitHub to determine the position of its @ user dropdown.

这是唯一可以保证处理与包装长线有关的所有边缘情况的方法。GitHub也用它来确定@用户下拉的位置。

#4


2  

JsFiddle of working example: http://jsfiddle.net/42zHC/2/

工作示例的JsFiddle: http://jsfiddle.net/42zHC/2/

Basically, we figure out how many columns fit in the width (since it will be monospace). We have to force scrollbars to always be there otherwise the calculation is off. Then we divide the number of columns that fit with the width, and we get the x offset per character. Then we set the line height on the textarea. Since we know how many characters are in a row, we can divide that with the number of characters and we get the row number. With the line height, we now have the y offset. Then we get the scrollTop of the textarea and subtract that, so that once it starts using the scrollbar, it still shows up in the right position.

基本上,我们算出宽度中有多少列(因为它是单空间的)。我们必须使滚动条始终保持在那里,否则计算就失败了,然后我们除以符合宽度的列数,得到每个字符的x偏移量。然后我们在textarea上设置行高度。因为我们知道一行中有多少个字符,我们可以用它除以字符数,得到行号。有了直线高度,我们现在有了y偏移量。然后我们得到textarea的滚动条并减去它,这样一旦它开始使用滚动条,它仍然会显示在正确的位置。

Javascript:

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:

HTML:

<textarea id="t"></textarea>
<br>
<span id="tooltip" style="background:yellow"></span>

CSS:

CSS:

textarea {
  height: 80px;
  line-height: 12px;
  overflow-y:scroll;
}
span {
  position: absolute;
}

#5


-1  

I couldn't get something similar to work, so my solution was to locate the character position of the caret in the textarea, cut out the current paragraph and display this next to the textarea.

我无法得到类似的东西,所以我的解决方案是在textarea中找到插入符号的字符位置,删除当前段落,并将其显示在textarea旁边。

Using the offset, I placed a fake cursor (div, display:inline, 1px wide, border-left: 1px solid black) in this view of the editable text.

使用偏移量,我在可编辑文本的视图中放置了一个伪游标(div, display:inline, 1px width, border-left: 1px solid black)。

This way, you can create a visual feedback area where you can show the result of effects (quite like * does when you write an answer).

通过这种方式,您可以创建一个视觉反馈区域,在那里您可以显示效果的结果(就像您编写答案时*所做的那样)。