在文本区域中找出光标所在的行号。

36
我希望能够找出并跟踪文本区域中光标所在行的“行号”(行数)。(更大的目标是,在创建/修改/选择新行时解析该行上的文本,如果当然没有粘贴文本。这样可以节省不必要的整个文本解析设置间隔时间。)
StackOverflow上有几篇文章,但没有一篇专门回答我的问题,大多数问题都是关于以像素为单位的光标位置或在文本区域旁边显示行编号。
我的尝试如下,在第 1 行开始并未离开文本区域时它运行得很好。当单击并返回到不同行的文本区域时,它会失败。当将文本粘贴到其中时,它也会失败,因为起始行不是 1。
我的JavaScript知识非常有限。
<html>

<head>
<title>DEVBug</title>

<script type="text/javascript">

    var total_lines = 1; // total lines
    var current_line = 1; // current line
    var old_line_count;

    // main editor function
    function code(e) {

        // declare some needed vars
        var keypress_code = e.keyCode; // key press
        var editor = document.getElementById('editor'); // the editor textarea
        var source_code = editor.value; // contents of the editor

        // work out how many lines we have used in total    
            var lines = source_code.split("\n");
            var total_lines = lines.length;

    // do stuff on key presses
    if (keypress_code == '13') { // Enter
        current_line += 1;
    } else if (keypress_code == '8') { // Backspace
        if (old_line_count > total_lines) { current_line -= 1; }
    } else if (keypress_code == '38') { // Up
        if (total_lines > 1 && current_line > 1) { current_line -= 1; }
    } else if (keypress_code == '40') { // Down
        if (total_lines > 1 && current_line < total_lines) { current_line += 1; }
    } else {
        //document.getElementById('keycodes').innerHTML += keypress_code;
    }

    // for some reason chrome doesn't enter a newline char on enter
    // you have to press enter and then an additional key for \n to appear
    // making the total_lines counter lag.
    if (total_lines < current_line) { total_lines += 1 };

    // putput the data
    document.getElementById('total_lines').innerHTML = "Total lines: " + total_lines;
    document.getElementById('current_line').innerHTML = "Current line: " + current_line;

    // save the old line count for comparison on next run
    old_line_count = total_lines;

}

</script>

</head>

<body>

<textarea id="editor" rows="30" cols="100" value="" onkeydown="code(event)"></textarea>
<div id="total_lines"></div>
<div id="current_line"></div>

</body>

</html>
4个回答

51
你需要使用selectionStart来实现这一点。
<textarea onkeyup="getLineNumber(this, document.getElementById('lineNo'));" onmouseup="this.onkeyup();"></textarea>
<div id="lineNo"></div>

<script>

    function getLineNumber(textarea, indicator) {

        indicator.innerHTML = textarea.value.substr(0, textarea.selectionStart).split("\n").length;
    }

</script>

当您使用鼠标改变光标位置时,这也有效。


40
如果文本区域中存在软换行符,此解决方案将无法使用。例如:创建一个有10个列的文本区域,在其中输入一些单词使文本溢出到2-3行,但不要在其中添加换行符。上面的代码将始终返回1,因为文本区域中没有"\n"字符,但是用户实际上看到多于1行。这是TEXTAREA的真正困难之处...我非常惊讶现代浏览器中没有任何标准的API来解决这个问题... - Jakub P.
2
这并不适用于所有情况。如果您输入的文本没有换行符,则上述函数始终返回1。但它可能是2或更多。 - Andrew Li

30

由于自动换行的缘故,这很困难。要计算换行符号的数量是一件非常容易的事情,但当新的一行是因为自动换行时会发生什么呢?为了解决这个问题,创建一个镜像非常有用(来源:github.com/jevin)。下面是思路:

  1. 创建一个文本区域的镜像
  2. 将从文本区域开始到光标位置的内容发送到镜像中去
  3. 使用镜像的高度提取当前行

在JSFiddle上查看

jQuery.fn.trackRows = function() {
    return this.each(function() {

    var ininitalHeight, currentRow, firstIteration = true;

    var createMirror = function(textarea) {
        jQuery(textarea).after('<div class="autogrow-textarea-mirror"></div>');
        return jQuery(textarea).next('.autogrow-textarea-mirror')[0];
    }

    var sendContentToMirror = function (textarea) {
        mirror.innerHTML = String(textarea.value.substring(0,textarea.selectionStart-1)).replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/'/g, '&#39;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\n/g, '<br />') + '.<br/>.';
        calculateRowNumber();
    }

    var growTextarea = function () {
        sendContentToMirror(this);
    }

    var calculateRowNumber = function () {
        if(firstIteration){
            ininitalHeight = $(mirror).height();
            currentHeight = ininitalHeight;
            firstIteration = false;
        } else {
            currentHeight = $(mirror).height();
        }
        // Assume that textarea.rows = 2 initially
        currentRow = currentHeight/(ininitalHeight/2) - 1;
        //remove tracker in production
        $('.tracker').html('Current row: ' + currentRow);
    }

    // Create a mirror
    var mirror = createMirror(this);

    // Style the mirror
    mirror.style.display = 'none';
    mirror.style.wordWrap = 'break-word';
    mirror.style.whiteSpace = 'normal';
    mirror.style.padding = jQuery(this).css('padding');
    mirror.style.width = jQuery(this).css('width');
    mirror.style.fontFamily = jQuery(this).css('font-family');
    mirror.style.fontSize = jQuery(this).css('font-size');
    mirror.style.lineHeight = jQuery(this).css('line-height');

    // Style the textarea
    this.style.overflow = "hidden";
    this.style.minHeight = this.rows+"em";

    var ininitalHeight = $(mirror).height();

    // Bind the textarea's event
    this.onkeyup = growTextarea;

    // Fire the event for text already present
    // sendContentToMirror(this);

    });
};

$(function(){
    $('textarea').trackRows();
});

6
应该接受这个答案。我没有测试过这段代码,但它至少尝试提供一个处理软换行的工作解决方案。 - ryandlf
样式是否是解决方案的必要部分?我在没有CSS的情况下实现了这个功能,并将我的文本区域包装属性设置为“off”。如果我在文本区域中输入文本,使其超出文本区域的边缘(强制出现水平滚动条),即使我没有创建新行,行号也会报告增加。 - youcantryreachingme
1
不幸的是,这也有未处理的边角情况。例如:在一个10列的文本区域中,如果你在第一行按下ctrl+right,它会将光标移动到第一行末尾的10个字符(textarea.selectionStart=10)。但是,如果你尝试通过rightleft将光标移动到同样的textarea.selectionStart=10位置,光标显然会出现在第二行的开头。 - Misha Akovantsev

1
我在发现可以获取光标的边界矩形以及其相对于父元素的y坐标时,成功解决了这个问题。
const editorCords = document.querySelector('#editor').getClientRects()[0]
const cursorCords = window.getSelection()?.getRangeAt(0).getClientRects()[0]
    
if (editorCords && cursorCords) {
   const line = Math.floor((cursorCords.y - editorCords.y) / cursorCords.height)
   console.log(line)
}

我还没有尝试过这个,但对于我想要知道光标是否在文本区域的第一行或最后一行的用例来说,这种方法可能是最好的。 - Pirijan

0
这对我有用:
function getLineNumber(textarea) {
  return textarea.value.substr(0, textarea.selectionStart) // get the substring of the textarea's value up to the cursor position
    .split("\n") // split on explicit line breaks
    .map((line) => 1 + Math.floor(line.length / textarea.cols)) // count the number of line wraps for each split and add 1 for the explicit line break
    .reduce((a, b) => a + b, 0); // add all of these together
};

受colab答案的启发,这个包括了换行次数,而不需要引入镜像(如bradbarbin的答案)。
诀窍就是简单地计算文本框列数textarea.cols可以将显式换行符\n之间的每个段落长度分成多少份。
注意:这从1开始计数。

3
很不幸,这种方法行不通,因为并非所有行在进行软换行之前都达到了完整的列数。 - Labrador

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接