Line Number Alignment in Text Wrapping

As you can see in the video, when line wrapping is enabled, line numbers do not represent the corresponding lines. On the other hand, in the screenshot, the way lines wrap around and create space between the line numbers positions the line numbers with the corresponding lines. I asked ChatGPT for help but he wasn't that helpful :(
4 Replies
No one
No one2mo ago
Here are the codes: html
<h1>Line number</h1>
<input type="checkbox" id="wrap-lines" class="wrap-lines" onchange="toggleWrap()">
<label for="wrap-lines" class="checkbox-label" >Wrap lines</label>
<div class="editor">
<div class="line-numbers disable-select"></div>
<textarea id="code" class="code" placeholder="Write something.. "></textarea>
</div>
<h1>Line number</h1>
<input type="checkbox" id="wrap-lines" class="wrap-lines" onchange="toggleWrap()">
<label for="wrap-lines" class="checkbox-label" >Wrap lines</label>
<div class="editor">
<div class="line-numbers disable-select"></div>
<textarea id="code" class="code" placeholder="Write something.. "></textarea>
</div>
css
body {
font-family: monospace;
}
/* Optional: Prevent text selection for specific elements */
.disable-select {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
#wrap-lines[type=checkbox] {
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
height: 20px;
width: 20px;
border: 1px solid black;
}
#wrap-lines[type=checkbox]::before {
content: ".";
color: transparent;
height: 14px;
width: 14px;
padding-top: 2px;
padding-left: 2px;
border: 1px solid white;
display: inline-block;
}
#wrap-lines[type=checkbox]:checked {
background-color: black;
}
.checkbox-label {
font-size: 16px;
}
.editor {
display: flex;
max-height: 50%;
border: solid black;
}
.line-numbers {
text-align: right;
font-size: 16px;
max-height: 50%;
overflow-y: auto;
padding-right: 5px;
padding-left: 5px;
padding-top: 2px;
}
.line-numbers::-webkit-scrollbar {
display: none;
}
.code {
flex: 1;
font-size: 16px;
outline: none;
border: none;
border-left: 0.5px solid black;
resize: none;
padding-top: 2px;
}
body {
font-family: monospace;
}
/* Optional: Prevent text selection for specific elements */
.disable-select {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
#wrap-lines[type=checkbox] {
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
height: 20px;
width: 20px;
border: 1px solid black;
}
#wrap-lines[type=checkbox]::before {
content: ".";
color: transparent;
height: 14px;
width: 14px;
padding-top: 2px;
padding-left: 2px;
border: 1px solid white;
display: inline-block;
}
#wrap-lines[type=checkbox]:checked {
background-color: black;
}
.checkbox-label {
font-size: 16px;
}
.editor {
display: flex;
max-height: 50%;
border: solid black;
}
.line-numbers {
text-align: right;
font-size: 16px;
max-height: 50%;
overflow-y: auto;
padding-right: 5px;
padding-left: 5px;
padding-top: 2px;
}
.line-numbers::-webkit-scrollbar {
display: none;
}
.code {
flex: 1;
font-size: 16px;
outline: none;
border: none;
border-left: 0.5px solid black;
resize: none;
padding-top: 2px;
}
javascript
// selectors
const textarea = document.querySelector('.code');
const lineNumbers = document.querySelector('.line-numbers');
const wrapLines = document.querySelector('#wrap-lines');

// Add event listeners
textarea.addEventListener('input', updateLineNumbers); // add new line numbers
textarea.addEventListener('scroll', syncScroll); // sync scrolling
lineNumbers.addEventListener('scroll', syncLineScroll); // sync scrolling

function toggleWrap() {
textarea.style.whiteSpace = wrapLines.checked ? 'pre-wrap' : 'nowrap';
}

// Synchronize vertical scrolling of line numbers with textarea
function syncScroll() {
lineNumbers.scrollTop = textarea.scrollTop;
}
// Synchronize vertical scrolling of textarea with line numbers.
function syncLineScroll() {
textarea.scrollTop = lineNumbers.scrollTop;
}

function updateLineNumbers() {
const lines = textarea.value.split('\n').length;
const lineNumbersHTML = Array.from({length: lines}, (_, i) => i + 1).join('<br>');
lineNumbers.innerHTML = lineNumbersHTML;
}
// Initial setup
updateLineNumbers();
// selectors
const textarea = document.querySelector('.code');
const lineNumbers = document.querySelector('.line-numbers');
const wrapLines = document.querySelector('#wrap-lines');

// Add event listeners
textarea.addEventListener('input', updateLineNumbers); // add new line numbers
textarea.addEventListener('scroll', syncScroll); // sync scrolling
lineNumbers.addEventListener('scroll', syncLineScroll); // sync scrolling

function toggleWrap() {
textarea.style.whiteSpace = wrapLines.checked ? 'pre-wrap' : 'nowrap';
}

// Synchronize vertical scrolling of line numbers with textarea
function syncScroll() {
lineNumbers.scrollTop = textarea.scrollTop;
}
// Synchronize vertical scrolling of textarea with line numbers.
function syncLineScroll() {
textarea.scrollTop = lineNumbers.scrollTop;
}

function updateLineNumbers() {
const lines = textarea.value.split('\n').length;
const lineNumbersHTML = Array.from({length: lines}, (_, i) => i + 1).join('<br>');
lineNumbers.innerHTML = lineNumbersHTML;
}
// Initial setup
updateLineNumbers();
Anyone?
MarkBoots
MarkBoots2mo ago
Can be done way easier, don't even need js
<label for="wrap">Wrap</label><input type="checkbox" id="wrap">
<ol contenteditable>
<li></li>
</ol>
<label for="wrap">Wrap</label><input type="checkbox" id="wrap">
<ol contenteditable>
<li></li>
</ol>
/* if not checked, turn off wrap */
#wrap:not(:checked) + ol { white-space: nowrap; overflow: auto }

/* to prevent the first line number from deleting */
ol > li:first-child::before{ content: "" }
/* if not checked, turn off wrap */
#wrap:not(:checked) + ol { white-space: nowrap; overflow: auto }

/* to prevent the first line number from deleting */
ol > li:first-child::before{ content: "" }
you can style li::marker for the numbering, or create it from scratch with a pseudo element and the counter() property https://codepen.io/MarkBoots/pen/BaeKBzZ --edit. hmm, i just noticed preventing the first li to be deleted does not work. I'll try to figure that out. (maybe we do need a bit of js for that)
clevermissfox
clevermissfox2mo ago
wondering if maybe something like ol:has(li:first-child:empty) li:first-child::before {content: '1.'; } mostly relying on the :empty pseudo-class or maybe :only-child . this approach though is genius just having the content-editable on the ol allows you to create lists like that! or perhaps
ol:empty::after {
content: '1.';
margin-inline-start: -2ch;
}
ol:empty::after {
content: '1.';
margin-inline-start: -2ch;
}
No one
No one2mo ago
Thank you both. I'll try this