← Back toMirror a text area

Display the line numbers in a text area

Written byPhuoc Nguyen
Created
24 Sep, 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:
html
<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:
css
.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:
js
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:
js
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:
js
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:
js
const displayLineNumbers = () => {
const lineNumbers = calculateLineNumbers();
lineNumbersEle.innerHTML = Array.from({
length: lineNumbers.length
}, (_, i) => `<div>${lineNumbers[i] || '&nbsp;'}</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 (`&nbsp;`). 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.
css
.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.
js
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:
js
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:
js
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

If you found this post 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!

Questions? 🙋

Do you have any questions about front-end development? If so, feel free to create a new issue on GitHub using the button below. I'm happy to help with any topic you'd like to learn more about, even beyond what's covered in this post.
While I have a long list of upcoming topics, I'm always eager to prioritize your questions and ideas for future content. Let's learn and grow together! Sharing knowledge is the best way to elevate ourselves 🥷.
Ask me questions

Recent posts ⚡

Newsletter 🔔

If you're into front-end technologies and you want to see more of the content I'm creating, then you might want to consider subscribing to my newsletter.
By subscribing, you'll be the first to know about new articles, products, and exclusive promotions.
Don't worry, I won't spam you. And if you ever change your mind, you can unsubscribe at any time.
Phước Nguyễn