Svelte中实现文本域自动换行(Word Wrap)时行号同步匹配换行效果
Svelte中实现文本域自动换行(Word Wrap)时行号同步匹配换行效果
嘿,我完全明白你遇到的问题了——当文本域里的长单词自动换行时,行号没法跟着同步“断行”,要么行号数量不够,要么和文本不对齐,确实影响体验。咱们来一步步解决这个问题!
问题根源
你原来的实现是基于**实际换行符(\n)**生成行号的,每个换行符对应一个行号元素。但浏览器的自动换行是纯排版行为,不会产生新的换行符,所以那些被自动拆分的行没有对应的行号元素,导致行号容器的高度和文本域脱节,行号自然没法和文本对齐。
解决方案思路
核心是让行号和文本的换行行为完全同步:我们需要计算每个实际行(由\n分隔的行)会被自动拆分成多少行,然后为每个自动换行的行生成相同的实际行号。具体来说,我们用一个临时元素模拟文本域的排版环境,计算每个实际行的自动换行次数,再生成对应的行号数组。
修改后的完整代码
<script lang="ts"> import { onMount } from 'svelte'; export let source = `jon sup me fixin to do sumthin good that is really long and will wrap around multiple lines in the textarea`; let lineNumbersArray: number[] = []; let textareaElement: HTMLTextAreaElement; // 计算并更新行号数组 function updateLineNumbers() { if (!textareaElement) return; // 创建临时元素模拟文本域的排版,计算每行的自动换行次数 const tempElement = document.createElement('span'); tempElement.style.fontFamily = 'monospace'; tempElement.style.lineHeight = '21px'; tempElement.style.whiteSpace = 'pre-wrap'; tempElement.style.position = 'absolute'; tempElement.style.visibility = 'hidden'; tempElement.style.width = `${textareaElement.offsetWidth}px`; document.body.appendChild(tempElement); const lines = source.split('\n'); lineNumbersArray = []; lines.forEach((line, index) => { // 空行也要计算高度,避免行号缺失 tempElement.textContent = line || ' '; // getClientRects()会返回每个自动换行后的行的位置信息 const lineCount = tempElement.getClientRects().length; // 为每个自动换行的行添加相同的行号 for (let i = 0; i < lineCount; i++) { lineNumbersArray.push(index + 1); } }); document.body.removeChild(tempElement); } const KeyUp = (event: Event) => { const textarea = event.target as HTMLTextAreaElement; source = textarea.value; updateLineNumbers(); }; const KeyDown = (event: Event) => { const textarea = event.target as HTMLTextAreaElement; if ((event as KeyboardEvent).key === 'Tab') { const start = textarea.selectionStart; const end = textarea.selectionEnd; if (start !== null && end !== null) { source = source.substring(0, start) + '\t' + source.substring(end); textarea.value = source; // 恢复光标位置,提升输入体验 textarea.selectionStart = start + 1; textarea.selectionEnd = start + 1; event.preventDefault(); updateLineNumbers(); } } }; // 组件挂载后初始化行号,监听窗口大小变化 onMount(() => { updateLineNumbers(); const handleResize = () => updateLineNumbers(); window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }); </script> <center> <div class="editor"> <div class="line-numbers"> {#each lineNumbersArray as lineNumber} <span class="line-number">{lineNumber}</span> {/each} </div> <textarea bind:this={textareaElement} class="text-content" on:keyup={KeyUp} on:keydown={KeyDown} bind:value={source} rows="15" /> </div> </center> <style> .editor { display: inline-flex; gap: 10px; font-family: monospace; line-height: 21px; background: #282a3a; border-radius: 2px; padding: 20px 10px; align-items: flex-start; /* 确保行号与文本顶部对齐 */ } .line-numbers { width: 20px; text-align: right; } .line-number { display: block; color: #506882; } .text-content { line-height: 21px; overflow-y: auto; /* 内容过多时允许滚动 */ padding: 0; border: 0; background: #282a3a; color: #fff; min-width: 500px; outline: none; resize: both; /* 允许调整文本域大小 */ white-space: pre-wrap; /* 开启自动换行 */ word-wrap: break-word; } </style>
关键细节说明
- 临时元素模拟排版:我们创建一个和文本域样式完全一致的临时元素,通过
getClientRects()获取每个实际行被自动拆分后的行数,确保行号数量和文本的实际行数完全匹配。 - 实时更新行号:监听文本内容变化、Tab键输入、窗口大小变化,随时重新计算行号数组,保证行号始终和文本对齐。
- CSS对齐优化:设置
align-items: flex-start让行号和文本顶部对齐,文本域开启white-space: pre-wrap和word-wrap: break-word确保自动换行行为正常。
现在测试一下效果:输入长文本时,自动换行的行都会显示对应的实际行号,行号和文本完全对齐,就像Notepad++或GitHub的代码块那样啦!
备注:内容来源于stack exchange,提问作者Jonathan
相关产品推荐
相关产品推荐

