为 HTML 文本区域添加行号

67

我有一个像下面代码中的 <textarea> 元素。如何在左边距上显示行号?

<TEXTAREA name="program" id="program" rows="15" cols="65" ></TEXTAREA>

1
代码是否必须以文本区域的形式呈现(即可编辑),还是可以在另一个块中格式化呈现? - Rob
10个回答

27

Alan Williamson编写了一个基于jQuery的Lined TextArea插件(链接已失效,请参考存档,适用于jQuery版本1.3或更高版本。
MIT许可证。


2
这似乎在IE中不兼容。 - ethicalhack3r
2
这个插件非常好,但是不能与textarea的"min-width"和"max-width" CSS属性一起使用。如果没有“width”,只有“min-width”,那么该插件会将错误的宽度(大约比实际宽度高16像素)设置给包装的textarea,因此浏览器会在文本上方呈现行号。 - ThatsMe
1
我添加了一个镜像站点,其中压缩文件和演示链接都可以正常工作。搜索“jquery插件行号文本框”会带来更新的解决方案。 - brasofilo

21
您可以尝试使用Code Mirror,这是一个JavaScript库,可嵌入网页中的代码编辑器。
它具有代码行功能,并且具有以下出色的特点:
  • 自动完成
  • 主题
  • 混合语言模式
  • 搜索
  • 合并/差异接口
  • 自定义滚动条等。

21
这是一个非常简单但有效的技巧。它插入了一张带有行号的图片。 唯一的问题是您可能需要创建自己的图片以匹配您的UI设计。

textarea.numbered {
    background: url(http://i.imgur.com/2cOaJ.png);
    background-attachment: local;
    background-repeat: no-repeat;
    padding-left: 35px;
    padding-top: 10px;
    border-color:#ccc;
}
<textarea cols="50" rows="10" class="numbered"></textarea>

致谢:Aakash Chakravarthy


12
虽然某些人将其称为解决方案,但我认为这只是一个临时应急措施。 - Gregor Weber
21
这个解决方案滚动不正确。此外,如果一行文本的宽度超过文本区域,它将换行。这意味着文本会继续在下一行,但是行号不会跟上。 - Scot Nery
1
这在Chrome和Safari中可以正常滚动,但在Firefox中完全无法滚动,我无法弄清原因... - Pit
3
缩放时它的比例不正确。 - Daniel
9
如果有人想知道,这张图片的数字范围是从“1至1500”。 - Samathingamajig
我成功地在Firefox中实现了它,方法是将textarea放在div内,并仅滚动div(对于div使用overflow-y: auto,对于textarea使用overflow-y: hidden)。但是,我不得不将textarea设置为巨大的高度,以便在向下滚动div时可以显示所有文本。 至于换行,我不得不通过使用white-space: pre样式来解决textarea。 - Leandro 86

17

TLDR: 使用 CodeMirror

这里有人推荐了CodeMirror,我强烈推荐使用!但是这个答案没有提供任何技术细节。

其他的解决方案:我试过的所有其他方案都存在行号与行不匹配的问题。我认为这是因为我的显示器DPI(每英寸像素数)是120%,而这些解决方案没有考虑这一点。

那么怎样使用CodeMirror呢?很容易!只需查看21000字的文档!我希望在一页或两页内就能解答你对它的99%的问题。

演示一下!

这是一个100%工作的演示,并且在StackOverflow沙箱中完美运行:

var editor = CodeMirror.fromTextArea(document.getElementById('code'), {
    lineNumbers: true,
    mode: 'text/x-perl',
    theme: 'abbott',
});
<script language="javascript" type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/codemirror.min.js"></script>
<script language="javascript" type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/mode/perl/perl.min.js"></script>

<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/codemirror.min.css"></link>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/theme/abbott.min.css"></link>

<textarea id="code" name="code">
if($cool_variable) {
    doTheCoolThing();     # it's PRETTY cool, imho
}</textarea>

特性!

  • 插入/覆盖模式。
  • 同时缩进多行。
  • 大量主题。

TLDR:如何在一页或更少的页面中使用CodeMirror

步骤1 - 加载基本核心库

将以下内容添加到您的 <head> 块中...

<script language="javascript" type="text/javascript" src="/static/js/codemirror-5.62.0/lib/codemirror.js"></script>
<link rel="stylesheet" type="text/css" href="/static/js/codemirror-5.62.0/lib/codemirror.css"></link>

如果想要额外的括号颜色匹配,也请加载此内容:

<script language="javascript" type="text/javascript" src="/codemirror-5.62.0/addon/edit/matchbrackets.js"></script>

第二步 - 设置语法高亮

检查 /codemirror-5.62.0/mode/ 文件夹以查看与您要编写代码的语言匹配的语言。在这个领域有广泛的支持

将以下内容添加到您的<head>区块中...

<script language="javascript" type="text/javascript" src="/static/js/codemirror-5.62.0/mode/perl/perl.js"></script>

第三步 - 初始化并展示CodeMirror

有一些要使用的文本区域....

<textarea id="code" name="code"></textarea>

在JS中初始化和设置您的CodeMirror。您需要使用Mimetype来指示您想要使用的模式,在这种情况下,我正在指示Perl Mimetype...

var editor = CodeMirror.fromTextArea(document.getElementById('code'), {
    lineNumbers: true,
    mode: 'text/x-perl',
    matchBrackets: true,
});

第4步 - 选择一个主题

选择你喜欢的一些主题,'liquibyte''cobalt''abbott'这几个主题都是相当不错的暗色模式风格。在定义editor之后运行以下代码...

editor.setOption('theme', 'cobalt');

就是这样了!


2
这一定是SO上最好记录的答案之一。如果OP想要显示其他支持的语言以外的内容,我可以推荐使用simplemode插件。 - mvreijn
CodeMirror 6很遗憾地没有预先构建的浏览器捆绑包。 - Deloo

13
没有人尝试使用HTML5画布对象来在其上绘制行号。因此,我成功地将画布和文本区域并排放置,并在画布上绘制了行号。

//
// desc: demonstrates textarea line numbers using canvas paint 
// auth: nikola bozovic <nigerija@gmail>
//
var TextAreaLineNumbersWithCanvas = function() {
  var div = document.getElementById('wrapper');
  var cssTable = 'padding:0px 0px 0px 0px!important; margin:0px 0px 0px 0px!important; font-size:1px;line-height:0px; width:auto;';
  var cssTd1 = 'border:1px #345 solid; border-right:0px; vertical-align:top; width:1px; background: #303030';
  var cssTd2 = 'border:1px #345 solid; border-left:0px; vertical-align:top;';
  var cssButton = 'width:120px; height:40px; border:1px solid #333 !important; border-bottom-color: #484!important; color:#ffe; background-color:#222;';
  var cssCanvas = 'border:0px; background-color:#1c1c20; margin-top:0px; padding-top:0px;';

  // LAYOUT (table 2 panels)
  var table = document.createElement('table');
  table.setAttribute('cellspacing', '0');
  table.setAttribute('cellpadding', '0');
  table.setAttribute('style', cssTable);
  var tr = document.createElement('tr');
  var td1 = document.createElement('td');
  td1.setAttribute('style', cssTd1);
  var td2 = document.createElement('td');
  td2.setAttribute('style', cssTd2);
  tr.appendChild(td1);
  tr.appendChild(td2);
  table.appendChild(tr);

  // TEXTAREA
  var ta = this.evalnode = document.getElementById('mytextarea');

  // TEXTAREA NUMBERS (Canvas)
  var canvas = document.createElement('canvas');
  canvas.width = 48; // must not set width & height in css !!!
  canvas.height = 500; // must not set width & height in css !!!
  canvas.setAttribute('style', cssCanvas);
  ta.canvasLines = canvas;
  td1.appendChild(canvas);
  td2.appendChild(ta);
  div.appendChild(table);

  // PAINT LINE NUMBERS
  ta.paintLineNumbers = function() {
    try {
      var canvas = this.canvasLines;
      if (canvas.height != this.clientHeight) canvas.height = this.clientHeight; // on resize
      var ctx = canvas.getContext("2d");
      ctx.fillStyle = "#303030";
      ctx.fillRect(0, 0, 42, this.scrollHeight + 1);
      ctx.fillStyle = "#808080";
      ctx.font = "11px monospace"; // NOTICE: must match TextArea font-size(11px) and lineheight(15) !!!
      var startIndex = Math.floor(this.scrollTop / 15, 0);
      var endIndex = startIndex + Math.ceil(this.clientHeight / 15, 0);
      for (var i = startIndex; i < endIndex; i++) {
        var ph = 10 - this.scrollTop + (i * 15);
        var text = '' + (1 + i); // line number
        ctx.fillText(text, 40 - (text.length * 6), ph);
      }
    } catch (e) {
      alert(e);
    }
  };
  ta.onscroll = function(ev) {
    this.paintLineNumbers();
  };
  ta.onmousedown = function(ev) {
    this.mouseisdown = true;
  }
  ta.onmouseup = function(ev) {
    this.mouseisdown = false;
    this.paintLineNumbers();
  };
  ta.onmousemove = function(ev) {
    if (this.mouseisdown) this.paintLineNumbers();
  };

  // make sure it's painted
  ta.paintLineNumbers();
  return ta;
};

var ta = TextAreaLineNumbersWithCanvas();
ta.value = TextAreaLineNumbersWithCanvas.toString();
#mytextarea {
  width: auto;
  height: 500px;
  font-size: 11px;
  font-family: monospace;
  line-height: 15px;
  font-weight: 500;
  margin: 0;
  padding: 0;
  resize: both;
  color: #ffa;
  border: 0;
  background-color: #222;
  white-space: pre;
  overflow: auto;
}


/* supported only in opera */
#mytextarea {
  scrollbar-arrow-color: #ee8;
  scrollbar-base-color: #444;
  scrollbar-track-color: #666;
  scrollbar-face-color: #444;
  /* outer light */
  scrollbar-3dlight-color: #444;
  /* inner light */
  scrollbar-highlight-color: #666;
  /* outer dark */
  scrollbar-darkshadow-color: #444;
  /* inner dark */
  scrollbar-shadow-color: #222;
}


/* chrome scrollbars */

textarea::-webkit-scrollbar {
  width: 16px;
  background-color: #444;
  cursor: pointer;
}

textarea::-webkit-scrollbar-track {
  background-color: #333;
  cursor: pointer;
}

textarea::-webkit-scrollbar-corner {
  background-color: #484;
  -webkit-box-shadow: inset 0 0 6px rgba(255, 255, 255, 0.3);
}

textarea::-webkit-scrollbar-thumb {
  background-color: #444;
  -webkit-box-shadow: inset 0 0 6px rgba(255, 255, 255, 0.3);
  cursor: pointer;
}
<div id="wrapper">
  <textarea id="mytextarea" cols="80" rows="10"></textarea>
</div>

这里有一个限制,我们不能在Paint()函数中轻松处理word-wrap,而不迭代整个文本区域的内容,并绘制到一个隐藏对象以测量每行的高度,否则将会产生非常复杂的代码。


真的,我没有在FireFox中测试过它。但我不认为它不能在FireFox中工作。到目前为止,它已经在IE11、Opera12、Edge、Chrome和变体、WebKit和变体以及一些支持W3C Canvas对象的其他浏览器中工作。有什么具体问题吗?感谢您提供信息。 - SoLaR
在Firefox中,我无法添加新行,无论我输入什么都是单行显示(即使我按“回车”键)。 - Mohasin Ali
我认为Firefox没有正确处理换行符,可以尝试使用CSS的"white-space: pre;"或其他组合。我注意到在过去,这是浏览器的一个巨大问题,似乎有些浏览器仍然存在同样的问题。 - SoLaR
因为在许多行之后,行号与文本区域的行不对齐,所以它无法很好地工作。我尝试过调整它,但经过几次尝试后放弃了。一开始我并不想用,但CodeMirror对我来说是一个解决方案。 - MagTun
不确定您进行了哪些调整,使用了多少行代码,或者使用了什么浏览器。我在Chrome中尝试了一百万行代码,行对齐效果非常好。只注意到Chrome在处理一百万行代码时会变慢,测量了paintLineNumbers()函数,只需要0~1毫秒的时间,猜测Chrome中的textarea控件速度较慢。 - SoLaR
好的解决方案!改进之处在于从元素样式中提取字体大小和行高值,而不是在脚本中硬编码它们。 - miken32

2

CodePress 是 WordPress 中使用的编辑器。


2

function generateWithNumber() {
  let inputTexts = document.getElementById("input").value
  let textsByLine = inputTexts.split("\n");
  const listMarkup = makeUL(textsByLine);
  document.getElementById("output").appendChild(listMarkup);
}
function makeUL(array) {
    let list = document.createElement('ol');
    for (let i = 0; i < array.length; i++) {
        let item = document.createElement('li');
        item.appendChild(document.createTextNode(array[i]));
        list.appendChild(item);
    }
    return list;
}
// document.getElementById('foo').appendChild(makeUL(options[0]));
ol {
  counter-reset: list;
}
ol > li {
  list-style: none;
}
ol > li:before {
  content: counter(list) ") ";
  counter-increment: list;
}
<textarea id="input"></textarea>
<button onClick=generateWithNumber() >Generate</button>
<p id="output"></p>

说明

这段代码定义了一个名为generateWithNumber()的函数,该函数在单击带有onClick事件的按钮时触发。此函数的目的是从<textarea>元素中获取输入文本,将其分割成行,并在<ol>(有序列表)元素中将行显示为带有编号的列表。


请记住,Stack Overflow 不仅旨在解决当前的问题,还要帮助未来的读者找到类似问题的解决方案,这需要理解底层代码。对于我们社区中的初学者和不熟悉语法的成员来说,这尤其重要。鉴于此,您能否编辑您的答案,包括您正在做什么以及为什么您认为这是最好的方法的解释 - Skully
@Skully-OnStrike 完成了! - SBimochan
请不要使用ChatGPT来生成回答,参见临时政策:禁止使用ChatGPT - Skully
@Skully 完成了... - SBimochan

2

Consider the use of a contenteditable ordered list <ol> instead of <textarea>

ol {
  font-family: monospace;
  white-space: pre; 
}

li::marker {
  font-size: 10px;
  color: grey;
}
<ol contenteditable><li>lorem ipsum      
<li>&gt;&gt; lorem ipsum
<li>lorem ipsum,\ 
<li>lorem ipsum.
<li>&gt;&gt; lorem ipsum
<li>lorem ipsum
<li>lorem ipsum
<li>lorem      
<li>ipsum
<li>&gt;&gt; lorem ipsum
<li>lorem ipsum
</ol> 

然而,::marker 样式似乎受限于 (list-style-type)。例如,去除句点或使用 vertical-align: super 等需要其他解决方法(回到 li:beforecounter)。
奖励: <li> 标签不需要闭合标签 </li>https://html.spec.whatwg.org/multipage/syntax.html#optional-tags),这样可以节省打字时间。
据我所知,codemirror 中的 <textarea> 只是在后台工作(Pseudo contenteditable: how does codemirror works?)。

2

行号

代码:

const textarea = document.querySelector("textarea");
      const numbers = document.querySelector(".numbers");
      textarea.addEventListener("keyup", (e) => {
        const num = e.target.value.split("\n").length;
        numbers.innerHTML = Array(num).fill("<span></span>").join("");
        
      });
      textarea.addEventListener("keydown", (event) => {
        if (event.key === "Tab") {
          const start = textarea.selectionStart;
          const end = textarea.selectionEnd;

          textarea.value =
            textarea.value.substring(0, start) +
            "\t" +
            textarea.value.substring(end);

          event.preventDefault();
        }
      });
   body {
        font-family: Consolas, "Courier New", Courier, monospace;
      }
      .editor {
        display: inline-flex;
        gap: 10px;
        font-family: Consolas, "Courier New", Courier, monospace;
        line-height: 21px;
        background-color: #282a3a;
        border-radius: 2px;
        padding: 20px 10px;
      }
      textarea {
        line-height: 21px;
        overflow-y: hidden;
        padding: 0;
        border: 0;
        background: #282a3a;
        color: #fff;
        min-width: 500px;
        outline: none;
        resize: none;
        font-family: Consolas, "Courier New", Courier, monospace;
      }
      .numbers {
        width: 20px;
        text-align: right;
      }

      .numbers span {
        counter-increment: linenumber;
      }

      .numbers span::before {
        content: counter(linenumber);
        display: block;
        color: #506882;
      }
<div class="editor">
      <div class="numbers">
        <span></span>
      </div>
      <textarea cols="30" rows="10"></textarea>
    </div>

From: https://www.webtips.dev/add-line-numbers-to-html-textarea

它实际上运行得相当不错。

行号并非瞬间出现,但速度相当快。


3
如果一行换了行,下一行的行号将显示不正确。 - InSync
这个回答是目前为止最好的。值得更多的点赞! - undefined

1
我创建了一个适用于带有换行的文本区域的行号系统。我还没有为单行溢出的代码进行定制,但如果你想要换行的行数,它是很好的。 CodePen演示

'use scrict';
var linenumbers = document.getElementById('line-numbers');
var editor = document.getElementById('codeblock');
    
function getWidth(elem) {
    return elem.scrollWidth - (parseFloat(window.getComputedStyle(elem, null).getPropertyValue('padding-left')) + parseFloat(window.getComputedStyle(elem, null).getPropertyValue('padding-right')))
}

function getFontSize(elem) {
    return parseFloat(window.getComputedStyle(elem, null).getPropertyValue('font-size'));
}

function cutLines(lines) {
    return lines.split(/\r?\n/);
}
    
    
function getLineHeight(elem) {
    var computedStyle = window.getComputedStyle(elem);
    var lineHeight = computedStyle.getPropertyValue('line-height');
    var lineheight;
    
    if (lineHeight === 'normal') {
        var fontSize = computedStyle.getPropertyValue('font-size');
        lineheight = parseFloat(fontSize) * 1.2;
    } else {
        lineheight = parseFloat(lineHeight);
    }
    
    return lineheight;
}

function getTotalLineSize(size, line, options) {
    if (typeof options === 'object') options = {};
    var p = document.createElement('span');
    p.style.setProperty('white-space', 'pre');
    p.style.display = 'inline-block';
    if (typeof options.fontSize !== 'undefined') p.style.fontSize = options.fontSize;
    p.innerHTML = line;
    document.body.appendChild(p);
    var result = (p.scrollWidth / size);
    p.remove();
    return Math.ceil(result);
}

function getLineNumber() {
    var textLines = editor.value.substr(0, editor.selectionStart).split("\n");
    var currentLineNumber = textLines.length;
    var currentColumnIndex = textLines[textLines.length-1].length;
    return currentLineNumber;
}
    
function init() {
    var totallines = cutLines(editor.value), linesize;
    linenumbers.innerHTML = '';
    for (var i = 1; i <= totallines.length; i++) {
        var num = document.createElement('p');
        num.innerHTML = i;
        linenumbers.appendChild(num);
            
        linesize = getTotalLineSize(getWidth(editor), totallines[(i - 1)], {'fontSize' : getFontSize(editor)});
        if (linesize > 1) {
            num.style.height = (linesize * getLineHeight(editor)) + 'px';
        }
    }
        
    linesize = getTotalLineSize(getWidth(editor), totallines[(getLineNumber() - 1)], {'fontSize' : getFontSize(editor)});
    if (linesize > 1) {
        linenumbers.childNodes[(getLineNumber() - 1)].style.height = (linesize * getLineHeight(editor)) + 'px';
    }
        
    editor.style.height = editor.scrollHeight;
    linenumbers.style.height = editor.scrollHeight;
}



editor.addEventListener('keyup', init);
editor.addEventListener('input', init);
editor.addEventListener('click', init);
editor.addEventListener('paste', init);
editor.addEventListener('load', init);
editor.addEventListener('mouseover', init);
#source-code {
    width: 100%;
    height: 450px;
    background-color: #2F2F2F;
    display: flex;
    justify-content: space-between;
    overflow-y: scroll;
    border-radius: 10px;
}

#source-code * {
    box-sizing: border-box;
}

#codeblock {
    white-space: pre-wrap;
    width: calc(100% - 30px);
    float: right;
    height: auto;
    font-family: arial;
    color: #fff;
    background: transparent;
    padding: 15px;
    line-height: 30px;
    overflow: hidden;
    min-height: 100%;
    border: none;
}

#line-numbers {
    min-width: 30px;
    height: 100%;
    padding: 15px 5px;
    font-size: 14px;
    vertical-align: middle;
    text-align: right;
    margin: 0;
    color: #fff;
    background: black;
}

#line-numbers p {
    display: block;
    height: 30px;
    line-height: 30px;
    margin: 0;
}

#codeblock:focus{
    outline: none;
}
<div id="source-code">
    <div id="line-numbers"><p>1</p></div>
    <textarea id="codeblock"></textarea>
</div>


除了一些改变,这对我来说真是神奇。谢谢。 - funtkungus

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