DEV Community

Cover image for Display the line numbers in a text area
Phuoc Nguyen
Phuoc Nguyen

Posted on • Originally published at phuoc.ng

Display the line numbers in a text area

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>
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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];
});
Enter fullscreen mode Exit fullscreen mode

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;
};
Enter fullscreen mode Exit fullscreen mode

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] || '&nbsp;'}</div>`).join('');
};
Enter fullscreen mode Exit fullscreen mode

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.

.container__lines {
    overflow: hidden;
}
Enter fullscreen mode Exit fullscreen mode

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;
});
Enter fullscreen mode Exit fullscreen mode

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();
});
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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:

Top comments (0)