马黑黑 发表于 2026-5-21 13:04

利用p标签的visibility属性给textarea加行号

<style>
        .artBox { font: normal 18px/1.5 sans-serif; overflow: auto; position: relative; }
        .artBox p { margin: 10px 0; }
        .artBox h1, .artBox h2 { margin: 8px 0; }
        .artBox code, .artBox pre { background: #f7f4f3; padding: 2px 6px; tab-size: 4; }
        .artBox pre { padding: 10px 20px; white-space: pre-wrap; word-wrap: break-word; }
        .artBox pre code { padding: 0; background: none; }
        .artBox blockquote { margin: 10px 20px; padding: 2px 15px; border-left: 3px solid skyblue; background: #e7e5e3; }
</style>

<div class="artBox">
    <p>CSS的visibility属性极具兼容性:</p>
    <blockquote>
      <p>自 2015年7月 起,visibility 属性已在主流浏览器中得到支持,可在大多数设备和浏览器版本中正常使用。</p>
    </blockquote>
    <p>该属性显示或隐藏元素而不更改文档的布局。并且,当主元素通过该属性设置为隐藏时,其子元素可通过设置该属性为显示依然得到正常渲染。</p>
    <p>根据 visibility 上述特性,我们可以不再拐弯抹角地为 textarea 创建行号了,也无需处理软换行垂直占位问题。具体思路是:在一个父元素的规范下垂直叠加、左右略微错开的两个元素,按文档流顺序安排,一为 div,二为 textarea,二者创建相同的影响排版布局的核心CSS属性值。div 根据 textarea 的输入动态添加 p 标签,p 标签 visibility 设置为 hidden,::before 或 ::after 伪元素用来显示行号、设置 visibility 为 visible,这样行号可以完美实现,且通过浏览器的 Ctrl+F 组合键查找关键字时查找结果不会成倍统计,因为 visibility 设为 hidden 的元素其上的文本浏览器会直接忽略。到目前止,这样的思路网上还没有人提到过。</p>
    <blockquote>
      <p>说明:通过 visibility 设置 p 标签及其伪元素还有一个好处:可以不用特别定位伪元素,这种情形之下使用 ::before 伪元素比较自然,它将出现在 p 段落的开头处。当然,如果需要精准定位水平方向行号显示的位置,那么 p 标签及其伪元素应做定位管理。</p>
    </blockquote>
    <p>以下是实现代码:</p>

        <div class="codebox" data-prev="1">
&lt;style&gt;
    /* 编辑器父元素 */
    .ed-container { margin: 30px auto; width: 800px; height: 450px; border: 1px solid gray; background: linear-gradient(90deg, #ddd 50px, white 50px); padding: 0; position: relative; }
    /* 行号p标签类选择器及其伪元素(其父元素属性设置交给JS完成) */
    .lnum { margin: 0; padding: 0; visibility: hidden; }
    .lnum::before { content: attr(data-num); width: 50px; color: #888; padding: 0; visibility: visible; }
    /* 编辑器 */
    #editor { position: absolute; left: 50px; width: calc(100% - 50px); height: 100%; box-sizing: border-box; padding: 8px; font-size: 18px; background: #fff; border: none; outline: none; resize: none; }
&lt;/style&gt;
&lt;!-- 编辑器HTML结构,行号容器将由JS生成 --&gt;
&lt;div class="ed-container"&gt;
    &lt;textarea id="editor"&gt;&lt;/textarea&gt;
&lt;/div&gt;
&lt;script&gt;
    // 函数:生成行号容器(参数 pa 是 textarea 的父元素)
    function createLnumBox(pa, textarea) {
      // 创建行号div元素
      const lineNumberBox = document.createElement('div');
      // 核心CSS属性
      const keyProperties = [
            'font', 'line-height', 'padding', 'margin', 'overflow',
            'width', 'height', 'white-space', 'word-break',
            'overflow-wrap', 'box-sizing', 'position'
      ];
      // 读取编辑器元素CSS属性
      const computedStyle = window.getComputedStyle(textarea);
      // 将核心CSS属性复刻到行号容器
      keyProperties.forEach(prop =&gt; {
            const value = computedStyle.getPropertyValue(prop);
            lineNumberBox.style = value;
      });
      pa.prepend(lineNumberBox); // 行号容器加到 textarea 之前
      return lineNumberBox; // 返回容器(便于后续操作)
    }
    // 函数:动态生成行号
    function mkLineNum(textarea, lnumDiv) {
      lnumDiv.innerHTML = ''; // 清空行号容器内容
      // 获取编辑器内容并按行拆分
      const lines = textarea.value.split('\n');
      const frg = new DocumentFragment(); // 创建文档碎片
      // 每一行都生成p标签加入到行号容器中
      lines.forEach((line, key) =&gt; {
            const p = document.createElement('p');
            p.className = 'lnum'; // 使用CSS预设的类名
            p.innerText = line ? line : '&lt;br&gt;'; // 空行使用换行标签(避免坍塌)
            p.dataset.num = key + 1; // p标签伪元素数据:行号
            frg.appendChild(p); // p标签加入到文档碎片
      });
      lnumDiv.appendChild(frg); // 文档碎片加入到行号div容器
    }
    const editor = document.getElementById('editor'); // 获取编辑器
    const container = document.querySelector('.ed-container'); // 获取编辑器父元素
    const lnum = createLnumBox(container, editor); // 创建行号容器
    // 编辑器输入事件:触发行号生成
    editor.addEventListener('input', (e) =&gt; {
      mkLineNum(editor, lnum);
    });
    // 编辑器滚动事件:保持行号同步翻滚
    editor.addEventListener('scroll', () =&gt; {
      lnum.scrollTop = editor.scrollTop;
    });
    // 页面初始化时加行号
    document.addEventListener('DOMContentLoaded', () =&gt; {
      mkLineNum(editor, lnum);
    });
&lt;/script&gt;
        </div>

    <p>上述基于textarea的行号案例:</p>
    <ul>
      <li>当前字号下支持最多4位数的行号显示,多了需要调整编辑框左移和行号底色占位尺寸;</li>
      <li>适用于小型项目,处理一千行上下的代码应该无压力;</li>
      <li>行号div容器也可以在CSS和HTML中维护但不建议,尽量在JS中复刻以确保容器和编辑器两个元素的核心属性相一致;</li>
      <li>案例演示仅实现了核心行号功能,其它功能需要自行加入;</li>
      <li>若用于生产环境,建议对编辑器对象进行更完备的封装。</li>
    </ul>
</div>

<script>
   function loadLineNumFile() {
       const script = document.createElement('script');
       script.charset = 'utf-8';
       script.src = 'https://638183.freep.cn/638183/web/helight/linenumber-2026.js';
       script.defer = true;
       script.onload = () => addLineNumber();
       document.head.appendChild(script);
   }

   loadLineNumFile();
</script>

梦江南 发表于 2026-5-21 13:14

谢谢黑黑老师辛苦,学习了。

霜染枫丹 发表于 2026-5-21 18:47

这天书看不懂就不看了,进来祝马老师小满快乐~~{:4_204:}{:4_190:}

红影 发表于 2026-5-21 21:55

这个是设计行号的么,感觉有些复杂呢。
辛苦黑黑,学习了{:4_187:}
页: [1]
查看完整版本: 利用p标签的visibility属性给textarea加行号