Twice the Challenge: Optimising Middle Truncation for Modern UIs

Anıl PakAnıl Pak
5 min read

When I landed my first tech job as a Full-Stack Developer, one of my first tasks was to create a middle truncate component. The goal was to handle long file names in a file uploader by truncating the middle portion of the file name while keeping the extension. For example, if a user uploaded a file with a long name, it would display something like this:
AnilPakFr…Performio.pdf


My First Attempt

As a Junior Developer, my first solution seemed simple and effective.

  1. Identified the index of the file extension (using the dot).

  2. Measured the total character length.

  3. Sliced the file name to keep the first and last few characters.

  4. Added three dots (...) in the middle, followed by the extension.

function getFileName(fileName: string): string {
  const dotIndex = fileName.lastIndexOf('.');
  if (dotIndex === -1 || dotIndex === 0) {
    // No extension found or invalid name, treat as a regular string
    return fileName.length > 22 
      ? fileName.substr(0, 11) + '...' + fileName.substr(-11) 
      : fileName;
  }

  const extension: string = fileName.substr(dotIndex); 
  const baseName: string = fileName.substr(0, dotIndex);

  if (baseName.length > 22) {
    return baseName.substr(0, 11) + '...' + baseName.substr(-11) + extension;
  }

  return fileName;
}

// Example Usage
const fileName: string = "AnilPakFrontEndPerformioComponent.pdf";
console.log(getFileName(fileName)); // Output: "AnilPakFron...mponent.pdf"

I also looked online for references and found similar approaches, which reassured me I was on the right track. It was straightforward and seemed practical at the time. However, I later realised it wasn’t an ideal solution.

The Problems with My Initial Solution

The first major flaw was that this approach didn’t account for the fact that not all characters have the same width (Different Font Families will have different character widths). For instance, the letters W and i are vastly different in size, and my method assumed equal width for all characters.

Additionally, I hadn’t considered responsiveness. On smaller screens, even the truncated names could overflow or break the layout. Supporting various screen sizes would mean creating multiple versions of the middle truncate logic — for instance, showing the first and last 8 characters on large screens but reducing that number as the screen size decreases.


A Fresh Perspective at My Second Job

When I started my second job as a Front-End Engineer, I encountered the same task: create a middle truncate component for a file uploader. However, this time, I wanted a dynamic solution that could handle responsiveness and automatically recalculate the truncated string whenever the container was resized.

Let’s walk through the steps I took to achieve this:

Step 1: Handling Responsiveness with ResizeObserver

The first challenge was ensuring the truncated file name adjusted dynamically when the container was resized. This required listening for changes in the container’s dimensions. I used the ResizeObserver API, which efficiently observes size changes in an element and triggers a callback.

const observer = new ResizeObserver(() => {
  // Recalculate truncation logic when container resizes
  const containerWidth = container.clientWidth; // Get the container's new width
});
observer.observe(container); // Start observing the container

This way, we no longer need to manually handle window resize events.

Step 2: Measuring the Width of Text

The next step was to measure the width of any given text. Different characters have varying widths (e.g., “W” is wider than “i”), so I created a helper function that temporarily adds an invisible <span> element to the DOM measures its width and then removes it.

const getTextWidth = (text: string, container: HTMLElement = document.body) => {
  const span = document.createElement('span');
  span.style.opacity = '0'; // Make the span invisible
  span.style.position = 'absolute'; // Position it off-screen
  span.style.whiteSpace = 'nowrap'; // Prevent wrapping
  span.innerText = text; // Set the text to measure
  container.appendChild(span); // Append span for measurement
  const width = span.clientWidth; // Get the width
  container.removeChild(span); // Clean up
  return width;
};

This function gives us an accurate width for any string in the current font and style.

Step 3: Calculating the Width of the Ellipsis Character

The ellipsis () is a key part of truncation, and its width also varies depending on the font. I used the same getTextWidth function to calculate its width within the container.

const getEllipsisWidth = (container: HTMLElement) =>
  getTextWidth('\u2026', container); // Unicode for the ellipsis character

Knowing the width of the ellipsis helps us determine how much space is left for the start and end parts of the truncated string.

Step 4: Determining the Maximum Length of the String

With the container width and ellipsis width in hand, I calculated how many characters of the file name could fit within the remaining space.

const getMaxStringLength = (containerWidth: number, ellipsisWidth: number) => {
  return Math.floor((containerWidth - ellipsisWidth) / getTextWidth('W')); // Assuming 'W' as the widest character
};

This calculation gives us a starting point to determine how much of the file name can fit before and after the ellipsis.

Step 5: Dynamically Truncating the String

Finally, I wrote a function to slice the string into a start segment, an ellipsis (), and an end segment, ensuring the total fits within the calculated maximum length.

const truncateString = (str: string, maxLength: number) => {
  if (str.length <= maxLength) return str; // If it already fits, return as is

  const ellipsis = '\u2026';
  const startLength = Math.ceil((maxLength - ellipsis.length) / 2);
  const endLength = Math.floor((maxLength - ellipsis.length) / 2);

  const start = str.slice(0, startLength);
  const end = str.slice(str.length - endLength);

  return start + ellipsis + end; // Combine start, ellipsis, and end
};

With the middle truncate applied, this is how the long file name will look!

Final Thoughts

Revisiting code you wrote a few years ago can be both eye-opening and humbling. You might wonder, Why did I do it that way? Why didn’t I come up with a better solution? But that’s all part of the growth process I think.

I’m glad I had the chance to revisit this challenge a year later and implement a more reliable, dynamic, and user-friendly approach :)
While this solution works well for me, I know there’s always room for improvement.

You might think this solution is a bit of overkill, but I don’t believe so :)

What about you? Have you encountered a similar challenge, or do you have an alternate way to handle middle truncation for long file names?

I’d love to hear your thoughts and ideas in the comments.

Here’s how the final code looks:

https://gist.github.com/anLpk/3b0830226461a0db324780e71779ee15

1
Subscribe to my newsletter

Read articles from Anıl Pak directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Anıl Pak
Anıl Pak

Software Engineer specialising in JavaScript, Web Accessibility, and Responsive Design. Currently working at Performio as a Fron-end Engineer.