Display the line numbers in a text area
Phuoc Nguyen
Posted on December 18, 2023
If you work with code, you know how helpful it is to have line numbers in a textarea. But did you know that line numbers can also be useful in many other situations? For example, when editing legal documents, writing poetry or lyrics, drafting screenplays and plays, or taking notes during a lecture.
Lawyers can use line numbers to quickly reference specific sections of legal documents. Poets and lyricists can easily find and edit specific lines. Screenwriters and playwrights can see the structure of their scripts more clearly. And students can keep track of important information discussed in class.
By adding line numbers to your textarea using JavaScript DOM, you can simplify and streamline these tasks. In this post, we'll guide you through the steps to display line numbers in a textarea using JavaScript DOM.
HTML markup
Let's start by creating the HTML for our textarea and line number display. Here's an example of what it should look like:
<div id="container" class="container">
<div id="line-numbers" class="container__lines"></div>
<textarea id="textarea" class="container__textarea"></textarea>
</div>
This code creates a container div
with two child elements: a div
for the line numbers and a text area.
To make everything look neat and tidy, we'll use flexbox. We apply the display: flex
property to the container element, which enables flexbox and lets us easily arrange the line numbers and text area within the container. We also add a border to our container element using the border
property, specifying a 1px solid line with an RGB color value of (203, 213, 225). To separate the line numbers div from the textarea, we set a right border for the line numbers div using the border-right
property.
These CSS properties give our textarea and line number display a clean and organized appearance. Here's an example of how we can add basic styles to the elements:
.container {
display: flex;
border: 1px solid rgb(203 213 225);
}
.container__textarea {
border: none;
}
.container__lines {
border-right: 1px solid rgb(203 213 225);
}
Displaying the line numbers
Displaying line numbers in a text area is a straightforward process. First, we determine the number of lines in the text area by splitting its content into different lines. Then, we create an array of div
elements representing the index of each line number. Finally, we join the array and populate the content of the line numbers element with the result.
Here's a code sample to help you visualize the idea:
const textarea = document.getElementById('textarea');
const lineNumbersEle = document.getElementById('line-numbers');
const displayLineNumbers = () => {
const lines = textarea.value.split('\n');
lineNumbersEle.innerHTML = Array.from({
length: lines.length,
}, (_, i) => `<div>${i + 1}</div>`).join('');
}
displayLineNumbers();
In this example, the displayLineNumbers
function generates line numbers and populates the line number display element. It splits the textarea value into lines using split('\n')
, creates an array of div
elements representing each line number using Array.from
and a map function that creates a div
element for each line number using template literals. It increments each index by one to match human-readable numbering.
Finally, all div
elements are joined into a single string using .join('')
, and the result is set back as HTML content for the line number display element.
But how does the idea work in reality? Let's take a look at the live demo below:
As you can see, there are two visible issues. First, the line numbers don't match the look and feel of the text area content . And secondly, their positions don't match their corresponding lines.
Don't worry, we'll fix those problems step by step in the next section. Let's move on.
Making line numbers match the text area
To ensure that the line numbers in a text area match the content, we need to dynamically determine the styles of the text area and apply them to the line numbers element. This is where the mirroring technique comes in handy. We can treat the line numbers element as a mirror of the text area.
To achieve this, we use the getComputedStyle
function to retrieve the computed styles of the text area. We then iterate through an array of relevant CSS properties, such as font family, font size, line height, and padding. For each property, we apply it as a style to our line numbers element.
By doing this, we can ensure that the line numbers element looks and feels like a natural extension of the text area.
Here's the code that accomplishes this:
const textareaStyles = window.getComputedStyle(textarea);
[
'fontFamily',
'fontSize',
'fontWeight',
'letterSpacing',
'lineHeight',
'padding',
].forEach((property) => {
lineNumbersEle.style[property] = textareaStyles[property];
});
By doing this, we ensure that our line numbers look exactly like the corresponding lines in the text area. This creates a consistent visual experience for users working with code or other content displayed in a textarea.
As a result, each line number is now horizontally aligned with its corresponding line in the text area.
Adjusting the position of line numbers
Earlier, we mentioned another issue where the line numbers don't align with the corresponding line in the text. This is because we split the text into multiple lines using the new line character (\n
), which is not the proper way to do it.
To determine the correct line number for a sentence, we need to consider how many lines the previous sentence takes up in the text area. Here's how we can calculate the line numbers:
const calculateNumLines = (str) => {
// Returns the total number of lines
// a given string takes up in the text area
};
const calculateLineNumbers = () => {
const lines = textarea.value.split('\n');
const numLines = lines.map((line) => calculateNumLines(line));
let lineNumbers = [];
let i = 1;
while (numLines.length > 0) {
const numLinesOfSentence = numLines.shift();
lineNumbers.push(i);
if (numLinesOfSentence > 1) {
Array(numLinesOfSentence - 1)
.fill('')
.forEach((_) => lineNumbers.push(''));
}
i++;
}
return lineNumbers;
};
In this example, the calculateNumLines
function calculates how many lines a given string takes up in the text area. Visit this page for more details on how it works.
The calculateLineNumbers
function is responsible for calculating the line numbers based on the number of lines taken up by each sentence in the textarea. First, it splits the textarea value into lines using split('\n')
. Then, for each line, it calculates how many lines that sentence takes up in the textarea using the calculateNumLines
function. The results are stored in an array called numLines
.
Next, it builds an array of line numbers using a while loop. The loop continues until all items in the numLines
array have been processed. Inside the loop, it pushes the current index (starting at 1) to an array called lineNumbers
. If a sentence takes up more than one line in the textarea, it fills those extra lines with empty strings and adds them to lineNumbers
.
After processing all sentences, our function returns lineNumbers
to ensure that each line number corresponds with its respective line and is correctly displayed in the text area, even when a line has no content.
Now, let's update our displayLineNumbers
function to handle empty line numbers:
const displayLineNumbers = () => {
const lineNumbers = calculateLineNumbers();
lineNumbersEle.innerHTML = Array.from({
length: lineNumbers.length
}, (_, i) => `<div>${lineNumbers[i] || ' '}</div>`).join('');
};
In this updated version of displayLineNumbers
, we use an OR operator to check whether each item in the lineNumbers
array is truthy. If it's not, we replace it with a non-breaking space (
). This simple change ensures that empty line numbers are displayed as expected.
With these changes in place, our textarea now has accurate and complete line numbering, even when some lines are empty.
Check out the demo below to see how the issue is resolved!
Keeping scroll positions in sync
When working with a text area that has a lot of content, you may notice that scrolling up or down causes the line numbers to stay in place. To fix this, we need to sync the scroll positions between the text area and the line numbers element.
First, we can disable the scrollbar in the line numbers element by setting the overflow
property to hidden
. This ensures that the line numbers stay in place and don't move around as the user scrolls through the content.
.container__lines {
overflow: hidden;
}
Next, we add an event listener to the text area that listens for the scroll
event. When this event fires, we update the scrollTop
property of the line numbers element to match the scrollTop
property of the text area. This keeps both elements in sync and ensures that the line numbers move with the text as the user scrolls.
textarea.addEventListener('scroll', () => {
lineNumbersEle.scrollTop = textarea.scrollTop;
});
By implementing this solution, our code now updates both elements in real-time, creating a smoother user experience.
Keeping line numbers up-to-date with text changes
To ensure that the line numbers always reflect the current state of the text area, we can listen for the input
event on the text area element. This event is triggered whenever a user types or pastes something into the text area.
In our event listener function, we simply call the displayLineNumbers
function again to recalculate and update the line numbers based on any changes made to the text area.
Here's how we can implement this:
textarea.addEventListener('input', () => {
displayLineNumbers();
});
This way, we make sure that our line numbers are always in sync with the text in the editing area. This creates a smooth and seamless experience for users as they work on their code or other content.
Automatically adjusting line numbers with text area resizing
Have you ever wanted to update line numbers when users resize a text area by dragging its bottom-right corner? Well, with the ResizeObserver
API, you can observe changes to an element's size and react accordingly.
First, create a new instance of ResizeObserver
and pass it a callback function that gets called whenever the observed element's size changes. Inside the callback function, get the current size of the text area using getBoundingClientRect()
. Then, set the height of the line numbers element to match the height of the text area.
Finally, call the displayLineNumbers
function again to recalculate and update the line numbers based on any changes made to the text area.
To ensure that your code runs whenever the user resizes the text area, observe it with the ResizeObserver
instance.
Here's a sample code to help you visualize the idea:
const ro = new ResizeObserver(() => {
const rect = textarea.getBoundingClientRect();
lineNumbersEle.style.height = `${rect.height}px`;
displayLineNumbers();
});
ro.observe(textarea);
With the implementation of this solution, our code now updates both elements in real-time as users resize their text areas.
Demo
It's time to see the final demo.
See also
It's highly recommended that you visit the original post to play with the interactive demos.
If you found this series helpful, please consider giving the repository a star on GitHub or sharing the post on your favorite social networks 😍. Your support would mean a lot to me!
If you want more helpful content like this, feel free to follow me:
Posted on December 18, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
July 24, 2024